mirror of
https://github.com/9001/copyparty.git
synced 2026-06-22 14:02:53 -06:00
760 lines
22 KiB
HTML
760 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" id="ht_brw">
|
|
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>{{ title }}</title>
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="width=device-width, initial-scale=0.8, minimum-scale=0.6">
|
|
<meta name="theme-color" content="#{{ tcolor }}">
|
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
|
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/browser.css?_={{ ts }}">
|
|
{{ html_head }}
|
|
{%- if css %}
|
|
<link rel="stylesheet" media="screen" href="{{ css }}_={{ ts }}">
|
|
{%- endif %}
|
|
</head>
|
|
|
|
<body>
|
|
<div id="ops"></div>
|
|
|
|
<div id="op_search" class="opview">
|
|
{%- if have_tags_idx %}
|
|
<div id="srch_form" class="tags opbox"></div>
|
|
{%- else %}
|
|
<div id="srch_form" class="opbox"></div>
|
|
{%- endif %}
|
|
<div id="srch_q"></div>
|
|
</div>
|
|
|
|
<div id="op_player" class="opview opbox opwide"></div>
|
|
|
|
<div id="op_bup" class="opview opbox {% if not ls0 %}act{% endif %}">
|
|
<div id="u2err"></div>
|
|
<form id="bup_form" method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
|
|
<input type="hidden" name="act" value="bput" />
|
|
<input type="file" id="bup_files" name="f" multiple /><br />
|
|
<input type="submit" id="bup_submit" value="start upload">
|
|
</form>
|
|
<div id="bup_progress" style="display:none; margin-top:10px;">
|
|
<div id="bup_status" style="margin-bottom:5px; font-weight:bold;"></div>
|
|
<div id="bup_fileinfo" style="margin-bottom:5px; font-size:0.9em;"></div>
|
|
<div id="bup_sizefmt" style="margin-bottom:5px; font-size:0.9em; color:#aaa;"></div>
|
|
<div style="width:100%; background:#333; height:20px; border-radius:10px; overflow:hidden; position:relative;">
|
|
<div id="bup_bar" style="width:0%; height:100%; background:linear-gradient(90deg, #09d, #4b0); transition:width 0.3s ease-out;"></div>
|
|
<div id="bup_bar_animate" style="position:absolute; top:0; left:0; right:0; bottom:0; background:linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent); animation:bup_shimmer 2s infinite; pointer-events:none;"></div>
|
|
</div>
|
|
<div id="bup_percent" style="text-align:center; margin-top:5px; font-size:0.9em; font-weight:bold;"></div>
|
|
<div id="bup_details" style="text-align:center; margin-top:5px; font-size:0.85em; color:#888;"></div>
|
|
<div id="bup_eta" style="text-align:center; margin-top:5px; font-size:0.9em; color:#0a8;"></div>
|
|
</div>
|
|
<div id="bup_result" style="display:none; margin-top:10px; padding:10px; border-radius:5px;"></div>
|
|
<a id="bbsw" href="?b=u" rel="nofollow"><br />switch to basic browser</a>
|
|
</div>
|
|
|
|
<div id="op_mkdir" class="opview opbox {% if not ls0 %}act{% endif %}">
|
|
<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
|
|
<input type="hidden" name="act" value="mkdir" />
|
|
📂<input type="text" name="name" class="i" placeholder="awesome mix vol.1">
|
|
<input type="submit" value="make directory">
|
|
</form>
|
|
</div>
|
|
|
|
<div id="op_new_md" class="opview opbox">
|
|
<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
|
|
<input type="hidden" name="act" value="new_md" />
|
|
📝<input type="text" name="name" class="i" placeholder="weekend-plans">
|
|
<input type="submit" value="new file">
|
|
</form>
|
|
<span id="new_mdi"></span>
|
|
</div>
|
|
|
|
<div id="op_msg" class="opview opbox {% if not ls0 %}act{% endif %}">
|
|
<form method="post" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" action="{{ url_suf }}">
|
|
📟<input type="text" name="msg" class="i" placeholder="lorem ipsum dolor sit amet">
|
|
<input type="submit" value="send msg to srv log">
|
|
</form>
|
|
</div>
|
|
|
|
<div id="op_unpost" class="opview opbox"></div>
|
|
|
|
<div id="op_up2k" class="opview"></div>
|
|
|
|
<div id="op_cfg" class="opview opbox opwide"></div>
|
|
|
|
<h1 id="path">
|
|
<a href="#" id="entree">🌲</a>
|
|
{%- for n in vpnodes %}
|
|
<a href="{{ r }}/{{ n[0] }}">{{ n[1] }}</a>
|
|
{%- endfor %}
|
|
</h1>
|
|
|
|
<div id="tree"></div>
|
|
|
|
<div id="wrap">
|
|
|
|
{%- if doc %}
|
|
<div id="bdoc"><pre>{{ doc|e }}</pre></div>
|
|
{%- else %}
|
|
<div id="bdoc"></div>
|
|
{%- endif %}
|
|
|
|
<div id="pro" class="logue">{{ "" if sb_lg else logues[0] }}</div>
|
|
|
|
<table id="files">
|
|
<thead>
|
|
<tr>
|
|
<th name="lead"><span>c</span></th>
|
|
<th name="href"><span>File Name</span></th>
|
|
<th name="sz" sort="int"><span>Size</span></th>
|
|
{%- for k in taglist %}
|
|
{%- if k.startswith('.') %}
|
|
<th name="tags/{{ k }}" sort="int"><span>{{ k[1:] }}</span></th>
|
|
{%- else %}
|
|
<th name="tags/{{ k }}"><span>{{ k[0]|upper }}{{ k[1:] }}</span></th>
|
|
{%- endif %}
|
|
{%- endfor %}
|
|
<th name="ext"><span>T</span></th>
|
|
<th name="ts"><span>Date</span></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
|
|
{%- for f in files %}
|
|
<tr><td>{{ f.lead }}</td><td><a href="{{ f.href }}">{{ f.name|e }}</a></td><td>{{ f.sz }}</td>
|
|
{%- if f.tags is defined %}
|
|
{%- for k in taglist %}<td>{{ f.tags[k]|e }}</td>{%- endfor %}
|
|
{%- endif %}<td>{{ f.ext }}</td><td>{{ f.dt }}</td></tr>
|
|
{%- endfor %}
|
|
|
|
</tbody>
|
|
</table>
|
|
|
|
<div id="epi" class="logue">{{ "" if sb_lg else logues[1] }}</div>
|
|
|
|
<h2 id="wfp"><a href="{{ r }}/?h" id="goh">control-panel</a></h2>
|
|
|
|
<a href="#" id="repl">π</a>
|
|
|
|
</div>
|
|
|
|
<div id="srv_info"><span>{{ srv_info }}</span></div>
|
|
|
|
<div id="widget"></div>
|
|
|
|
<div id="rcm" tabindex="0"></div>
|
|
|
|
<script>
|
|
var SR = "{{ r }}",
|
|
CGV1 = {{ cgv1 }},
|
|
CGV = {{ cgv|tojson }},
|
|
TS = "{{ ts }}",
|
|
dtheme = "{{ dtheme }}",
|
|
lang = "{{ lang }}",
|
|
dfavico = "{{ favico }}",
|
|
have_tags_idx = {{ have_tags_idx }},
|
|
logues = {{ logues|tojson if sb_lg else "[]" }},
|
|
ls0 = {{ ls0|tojson }};
|
|
|
|
var STG = window.localStorage;
|
|
document.documentElement.className = (STG && STG.cpp_thm) || dtheme;
|
|
</script>
|
|
<script src="{{ r }}/.cpr/w/util.js?_={{ ts }}"></script>
|
|
{%- if lang != "eng" %}
|
|
<script src="{{ r }}/.cpr/w/tl/{{ lang }}.js?_={{ ts }}"></script>
|
|
{%- endif %}
|
|
<script src="{{ r }}/.cpr/w/baguettebox.js?_={{ ts }}"></script>
|
|
<script src="{{ r }}/.cpr/w/browser.js?_={{ ts }}"></script>
|
|
<script src="{{ r }}/.cpr/w/up2k.js?_={{ ts }}"></script>
|
|
{%- if js %}
|
|
<script src="{{ js }}_={{ ts }}"></script>
|
|
{%- endif %}
|
|
<script>
|
|
Date.now();function jsldp(a,b){2!=window[a]&&alert("FATAL ERROR: cannot load "+b+".js due to unreliable network or broken reverse-proxy; try CTRL-SHIFT-R")}
|
|
jsldp("J_UTL","util");
|
|
jsldp("J_BBX","baguettebox");
|
|
jsldp("J_BRW","browser");
|
|
jsldp("J_U2K","up2k");
|
|
</script>
|
|
<script>
|
|
(function() {
|
|
var CONFIG = {
|
|
MAX_RETRIES: 3,
|
|
INITIAL_RETRY_DELAY: 1000,
|
|
MAX_RETRY_DELAY: 30000,
|
|
UPLOAD_TIMEOUT: 300000,
|
|
SPEED_WINDOW_SIZE: 10,
|
|
ETA_MIN_SAMPLES: 3,
|
|
RETRYABLE_STATUSES: [0, 408, 425, 429, 500, 502, 503, 504]
|
|
};
|
|
|
|
var state = {
|
|
progressDiv: null,
|
|
progressBar: null,
|
|
progressPercent: null,
|
|
progressDetails: null,
|
|
progressEta: null,
|
|
progressStatus: null,
|
|
progressFileinfo: null,
|
|
progressSizefmt: null,
|
|
resultDiv: null,
|
|
form: null,
|
|
submitBtn: null,
|
|
fileInput: null,
|
|
currentFiles: [],
|
|
currentFileIndex: 0,
|
|
totalFilesSize: 0,
|
|
currentXhr: null,
|
|
isCancelled: false,
|
|
uploadStartTime: 0,
|
|
currentRetry: 0,
|
|
speedSamples: [],
|
|
lastLoaded: 0,
|
|
lastSampleTime: 0,
|
|
animationFrame: null,
|
|
smoothedPercent: 0,
|
|
targetPercent: 0
|
|
};
|
|
|
|
function formatSize(bytes) {
|
|
if (bytes === 0) return '0 B';
|
|
var k = 1024,
|
|
sizes = ['B', 'KB', 'MB', 'GB', 'TB'],
|
|
i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
function formatSpeed(bytesPerSec) {
|
|
if (bytesPerSec === 0 || !isFinite(bytesPerSec)) return '0 B/s';
|
|
var k = 1024,
|
|
sizes = ['B/s', 'KB/s', 'MB/s', 'GB/s'],
|
|
i = Math.floor(Math.log(bytesPerSec) / Math.log(k));
|
|
return parseFloat((bytesPerSec / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
function formatDuration(seconds) {
|
|
if (!isFinite(seconds) || seconds < 0) return 'calculating...';
|
|
if (seconds === 0) return 'done';
|
|
|
|
seconds = Math.round(seconds);
|
|
|
|
if (seconds < 60) {
|
|
return seconds + 's';
|
|
} else if (seconds < 3600) {
|
|
var m = Math.floor(seconds / 60);
|
|
var s = seconds % 60;
|
|
return m + 'm ' + s + 's';
|
|
} else {
|
|
var h = Math.floor(seconds / 3600);
|
|
var m = Math.floor((seconds % 3600) / 60);
|
|
var s = seconds % 60;
|
|
return h + 'h ' + m + 'm ' + s + 's';
|
|
}
|
|
}
|
|
|
|
function formatTime(ms) {
|
|
var date = new Date(ms);
|
|
return date.toLocaleTimeString();
|
|
}
|
|
|
|
function isRetryableError(xhr) {
|
|
var status = xhr.status;
|
|
|
|
if (CONFIG.RETRYABLE_STATUSES.indexOf(status) !== -1) {
|
|
return true;
|
|
}
|
|
|
|
if (status === 0) {
|
|
return true;
|
|
}
|
|
|
|
if (status >= 500 && status < 600) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function calculateRetryDelay(retryCount) {
|
|
var delay = CONFIG.INITIAL_RETRY_DELAY * Math.pow(2, retryCount);
|
|
var jitter = delay * 0.2 * (Math.random() - 0.5);
|
|
delay = delay + jitter;
|
|
return Math.min(delay, CONFIG.MAX_RETRY_DELAY);
|
|
}
|
|
|
|
function resetSpeedTracking() {
|
|
state.speedSamples = [];
|
|
state.lastLoaded = 0;
|
|
state.lastSampleTime = 0;
|
|
}
|
|
|
|
function addSpeedSample(loaded, total, currentTime) {
|
|
if (state.lastSampleTime === 0) {
|
|
state.lastLoaded = loaded;
|
|
state.lastSampleTime = currentTime;
|
|
return;
|
|
}
|
|
|
|
var timeDiff = (currentTime - state.lastSampleTime) / 1000;
|
|
var bytesDiff = loaded - state.lastLoaded;
|
|
|
|
if (timeDiff > 0 && bytesDiff >= 0) {
|
|
var speed = bytesDiff / timeDiff;
|
|
|
|
state.speedSamples.push({
|
|
speed: speed,
|
|
time: currentTime,
|
|
bytes: bytesDiff,
|
|
seconds: timeDiff
|
|
});
|
|
|
|
while (state.speedSamples.length > CONFIG.SPEED_WINDOW_SIZE) {
|
|
state.speedSamples.shift();
|
|
}
|
|
}
|
|
|
|
state.lastLoaded = loaded;
|
|
state.lastSampleTime = currentTime;
|
|
}
|
|
|
|
function calculateAverageSpeed() {
|
|
if (state.speedSamples.length === 0) {
|
|
return 0;
|
|
}
|
|
|
|
var totalBytes = 0;
|
|
var totalSeconds = 0;
|
|
|
|
for (var i = 0; i < state.speedSamples.length; i++) {
|
|
totalBytes += state.speedSamples[i].bytes;
|
|
totalSeconds += state.speedSamples[i].seconds;
|
|
}
|
|
|
|
if (totalSeconds === 0) {
|
|
return 0;
|
|
}
|
|
|
|
return totalBytes / totalSeconds;
|
|
}
|
|
|
|
function calculateETA(loaded, total) {
|
|
if (total === 0) return 0;
|
|
|
|
var remaining = total - loaded;
|
|
if (remaining <= 0) return 0;
|
|
|
|
var avgSpeed = calculateAverageSpeed();
|
|
|
|
if (avgSpeed === 0) {
|
|
return Infinity;
|
|
}
|
|
|
|
return remaining / avgSpeed;
|
|
}
|
|
|
|
function smoothProgress(nowPercent) {
|
|
state.targetPercent = nowPercent;
|
|
|
|
if (!state.animationFrame) {
|
|
animateProgress();
|
|
}
|
|
}
|
|
|
|
function animateProgress() {
|
|
var diff = state.targetPercent - state.smoothedPercent;
|
|
|
|
if (Math.abs(diff) < 0.01) {
|
|
state.smoothedPercent = state.targetPercent;
|
|
state.animationFrame = null;
|
|
updateProgressDisplay(state.smoothedPercent);
|
|
return;
|
|
}
|
|
|
|
var step = diff * 0.15;
|
|
state.smoothedPercent += step;
|
|
|
|
updateProgressDisplay(state.smoothedPercent);
|
|
|
|
state.animationFrame = requestAnimationFrame(animateProgress);
|
|
}
|
|
|
|
function updateProgressDisplay(percent) {
|
|
if (state.progressBar) {
|
|
state.progressBar.style.width = percent + '%';
|
|
}
|
|
if (state.progressPercent) {
|
|
state.progressPercent.textContent = percent.toFixed(1) + '%';
|
|
}
|
|
}
|
|
|
|
function updateProgressUI(loaded, total, currentTime) {
|
|
if (!state.progressBar || !state.progressPercent) return;
|
|
|
|
var percent = total > 0 ? (loaded / total * 100) : 0;
|
|
|
|
smoothProgress(percent);
|
|
|
|
addSpeedSample(loaded, total, currentTime);
|
|
|
|
var avgSpeed = calculateAverageSpeed();
|
|
var eta = calculateETA(loaded, total);
|
|
|
|
var details = '';
|
|
details += formatSize(loaded) + ' / ' + formatSize(total);
|
|
|
|
if (avgSpeed > 0) {
|
|
details += ' | ' + formatSpeed(avgSpeed);
|
|
}
|
|
|
|
if (state.progressDetails) {
|
|
state.progressDetails.textContent = details;
|
|
}
|
|
|
|
if (state.speedSamples.length >= CONFIG.ETA_MIN_SAMPLES && avgSpeed > 0) {
|
|
if (isFinite(eta)) {
|
|
var etaText = 'Remaining: ' + formatDuration(eta);
|
|
if (eta > 0 && eta < Infinity) {
|
|
var completionTime = currentTime + (eta * 1000);
|
|
etaText += ' (est. ' + formatTime(completionTime) + ')';
|
|
}
|
|
if (state.progressEta) {
|
|
state.progressEta.textContent = etaText;
|
|
}
|
|
}
|
|
} else {
|
|
if (state.progressEta) {
|
|
state.progressEta.textContent = 'Remaining: calculating...';
|
|
}
|
|
}
|
|
|
|
if (state.progressSizefmt) {
|
|
state.progressSizefmt.textContent = 'Speed: ' + formatSpeed(avgSpeed);
|
|
}
|
|
}
|
|
|
|
function showProgress(show) {
|
|
if (state.progressDiv) {
|
|
state.progressDiv.style.display = show ? 'block' : 'none';
|
|
}
|
|
}
|
|
|
|
function showResult(success, message) {
|
|
if (state.resultDiv) {
|
|
state.resultDiv.style.display = 'block';
|
|
state.resultDiv.style.background = success ? '#050' : '#500';
|
|
state.resultDiv.style.color = '#fff';
|
|
state.resultDiv.innerHTML = message;
|
|
}
|
|
}
|
|
|
|
function hideResult() {
|
|
if (state.resultDiv) {
|
|
state.resultDiv.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function setStatus(text) {
|
|
if (state.progressStatus) {
|
|
state.progressStatus.textContent = text;
|
|
}
|
|
}
|
|
|
|
function setFileinfo(text) {
|
|
if (state.progressFileinfo) {
|
|
state.progressFileinfo.textContent = text;
|
|
}
|
|
}
|
|
|
|
function setFormEnabled(enabled) {
|
|
if (state.submitBtn) {
|
|
state.submitBtn.disabled = !enabled;
|
|
state.submitBtn.value = enabled ? 'start upload' : 'uploading...';
|
|
}
|
|
if (state.fileInput) {
|
|
state.fileInput.disabled = !enabled;
|
|
}
|
|
}
|
|
|
|
function uploadFile(file, formAction, callback) {
|
|
state.currentXhr = null;
|
|
state.isCancelled = false;
|
|
state.uploadStartTime = Date.now();
|
|
state.currentRetry = 0;
|
|
|
|
resetSpeedTracking();
|
|
state.smoothedPercent = 0;
|
|
state.targetPercent = 0;
|
|
|
|
setStatus('Uploading: ' + file.name);
|
|
setFileinfo('Size: ' + formatSize(file.size) + ' | Retry: 0/' + CONFIG.MAX_RETRIES);
|
|
|
|
if (state.progressEta) {
|
|
state.progressEta.textContent = '';
|
|
}
|
|
if (state.progressDetails) {
|
|
state.progressDetails.textContent = '0 B / ' + formatSize(file.size);
|
|
}
|
|
if (state.progressSizefmt) {
|
|
state.progressSizefmt.textContent = '';
|
|
}
|
|
|
|
var formData = new FormData();
|
|
formData.append('act', 'bput');
|
|
formData.append('f', file);
|
|
|
|
function doUpload() {
|
|
if (state.isCancelled) {
|
|
return;
|
|
}
|
|
|
|
var xhr = new XMLHttpRequest();
|
|
state.currentXhr = xhr;
|
|
|
|
xhr.upload.onprogress = function(e) {
|
|
if (e.lengthComputable && !state.isCancelled) {
|
|
updateProgressUI(e.loaded, e.total, Date.now());
|
|
}
|
|
};
|
|
|
|
xhr.onload = function() {
|
|
if (state.isCancelled) {
|
|
return;
|
|
}
|
|
|
|
var status = xhr.status;
|
|
var responseText = xhr.responseText || '';
|
|
|
|
if (status === 200 || status === 201) {
|
|
if (responseText.indexOf('ERROR') === -1 &&
|
|
responseText.indexOf('error:') === -1 &&
|
|
responseText.indexOf('ERR') === -1) {
|
|
state.currentXhr = null;
|
|
callback(null, xhr);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (isRetryableError(xhr) && state.currentRetry < CONFIG.MAX_RETRIES) {
|
|
state.currentRetry++;
|
|
var delay = calculateRetryDelay(state.currentRetry);
|
|
|
|
setFileinfo('Size: ' + formatSize(file.size) +
|
|
' | Retry: ' + state.currentRetry + '/' + CONFIG.MAX_RETRIES +
|
|
' - waiting ' + (delay / 1000).toFixed(1) + 's...');
|
|
|
|
resetSpeedTracking();
|
|
|
|
setTimeout(function() {
|
|
if (!state.isCancelled) {
|
|
setFileinfo('Size: ' + formatSize(file.size) +
|
|
' | Retry: ' + state.currentRetry + '/' + CONFIG.MAX_RETRIES +
|
|
' - retrying...');
|
|
doUpload();
|
|
}
|
|
}, delay);
|
|
return;
|
|
}
|
|
|
|
state.currentXhr = null;
|
|
var errMsg = 'Upload failed';
|
|
|
|
if (status === 0) {
|
|
errMsg = 'Network error - connection failed';
|
|
} else if (status >= 500) {
|
|
errMsg = 'Server error (' + status + ')';
|
|
} else if (status === 403) {
|
|
errMsg = 'Permission denied';
|
|
} else if (status === 404) {
|
|
errMsg = 'Target folder not found';
|
|
} else if (status === 413) {
|
|
errMsg = 'File too large';
|
|
} else if (status !== 200 && status !== 201) {
|
|
errMsg = 'HTTP error: ' + status;
|
|
}
|
|
|
|
if (responseText && responseText.length < 500) {
|
|
errMsg += ' - ' + responseText.trim();
|
|
}
|
|
|
|
callback(new Error(errMsg), xhr);
|
|
};
|
|
|
|
xhr.onerror = function() {
|
|
if (state.isCancelled) {
|
|
return;
|
|
}
|
|
|
|
if (state.currentRetry < CONFIG.MAX_RETRIES) {
|
|
state.currentRetry++;
|
|
var delay = calculateRetryDelay(state.currentRetry);
|
|
|
|
setFileinfo('Size: ' + formatSize(file.size) +
|
|
' | Retry: ' + state.currentRetry + '/' + CONFIG.MAX_RETRIES +
|
|
' - network error, retrying in ' + (delay / 1000).toFixed(1) + 's...');
|
|
|
|
resetSpeedTracking();
|
|
|
|
setTimeout(function() {
|
|
if (!state.isCancelled) {
|
|
doUpload();
|
|
}
|
|
}, delay);
|
|
return;
|
|
}
|
|
|
|
state.currentXhr = null;
|
|
callback(new Error('Network error after ' + CONFIG.MAX_RETRIES + ' retries'), xhr);
|
|
};
|
|
|
|
xhr.ontimeout = function() {
|
|
if (state.isCancelled) {
|
|
return;
|
|
}
|
|
|
|
if (state.currentRetry < CONFIG.MAX_RETRIES) {
|
|
state.currentRetry++;
|
|
var delay = calculateRetryDelay(state.currentRetry);
|
|
|
|
setFileinfo('Size: ' + formatSize(file.size) +
|
|
' | Retry: ' + state.currentRetry + '/' + CONFIG.MAX_RETRIES +
|
|
' - timeout, retrying in ' + (delay / 1000).toFixed(1) + 's...');
|
|
|
|
resetSpeedTracking();
|
|
|
|
setTimeout(function() {
|
|
if (!state.isCancelled) {
|
|
doUpload();
|
|
}
|
|
}, delay);
|
|
return;
|
|
}
|
|
|
|
state.currentXhr = null;
|
|
callback(new Error('Upload timeout after ' + CONFIG.MAX_RETRIES + ' retries'), xhr);
|
|
};
|
|
|
|
xhr.onabort = function() {
|
|
if (state.isCancelled) {
|
|
return;
|
|
}
|
|
state.currentXhr = null;
|
|
callback(new Error('Upload aborted'), xhr);
|
|
};
|
|
|
|
xhr.timeout = CONFIG.UPLOAD_TIMEOUT;
|
|
xhr.open('POST', formAction, true);
|
|
xhr.send(formData);
|
|
}
|
|
|
|
doUpload();
|
|
}
|
|
|
|
function uploadNextFile(formAction) {
|
|
if (state.currentFileIndex >= state.currentFiles.length) {
|
|
showProgress(false);
|
|
showResult(true,
|
|
'<strong>Upload complete!</strong><br>' +
|
|
'Files: ' + state.currentFiles.length + '<br>' +
|
|
'Total size: ' + formatSize(state.totalFilesSize) + '<br>' +
|
|
'Time: ' + formatDuration((Date.now() - state.uploadStartTime) / 1000)
|
|
);
|
|
setFormEnabled(true);
|
|
return;
|
|
}
|
|
|
|
var file = state.currentFiles[state.currentFileIndex];
|
|
|
|
uploadFile(file, formAction, function(err, xhr) {
|
|
if (err) {
|
|
showProgress(false);
|
|
showResult(false,
|
|
'<strong>Upload failed</strong><br>' +
|
|
'File: ' + file.name + '<br>' +
|
|
'Error: ' + err.message
|
|
);
|
|
setFormEnabled(true);
|
|
} else {
|
|
state.currentFileIndex++;
|
|
uploadNextFile(formAction);
|
|
}
|
|
});
|
|
}
|
|
|
|
function initBupUpload() {
|
|
state.progressDiv = document.getElementById('bup_progress');
|
|
state.progressBar = document.getElementById('bup_bar');
|
|
state.progressPercent = document.getElementById('bup_percent');
|
|
state.progressDetails = document.getElementById('bup_details');
|
|
state.progressEta = document.getElementById('bup_eta');
|
|
state.progressStatus = document.getElementById('bup_status');
|
|
state.progressFileinfo = document.getElementById('bup_fileinfo');
|
|
state.progressSizefmt = document.getElementById('bup_sizefmt');
|
|
state.resultDiv = document.getElementById('bup_result');
|
|
state.form = document.getElementById('bup_form');
|
|
state.submitBtn = document.getElementById('bup_submit');
|
|
state.fileInput = document.getElementById('bup_files');
|
|
|
|
if (!state.form || !state.submitBtn || !state.fileInput) {
|
|
console.log('Basic upload form not found');
|
|
return;
|
|
}
|
|
|
|
var style = document.createElement('style');
|
|
style.textContent = '\
|
|
@keyframes bup_shimmer {\
|
|
0% { transform: translateX(-100%); }\
|
|
100% { transform: translateX(100%); }\
|
|
}\
|
|
#bup_bar_animate {\
|
|
animation: bup_shimmer 1.5s infinite linear;\
|
|
}\
|
|
';
|
|
document.head.appendChild(style);
|
|
|
|
state.form.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
if (!state.fileInput.files || state.fileInput.files.length === 0) {
|
|
showResult(false, 'Please select at least one file to upload.');
|
|
return;
|
|
}
|
|
|
|
hideResult();
|
|
showProgress(true);
|
|
setFormEnabled(false);
|
|
|
|
state.currentFiles = Array.from(state.fileInput.files);
|
|
state.currentFileIndex = 0;
|
|
state.totalFilesSize = 0;
|
|
state.uploadStartTime = Date.now();
|
|
|
|
for (var i = 0; i < state.currentFiles.length; i++) {
|
|
state.totalFilesSize += state.currentFiles[i].size;
|
|
}
|
|
|
|
setStatus('Preparing upload...');
|
|
setFileinfo('Total: ' + state.currentFiles.length + ' file(s), ' + formatSize(state.totalFilesSize));
|
|
|
|
var formAction = state.form.action || window.location.href;
|
|
|
|
if (formAction.indexOf('?') === -1) {
|
|
formAction += '?j';
|
|
} else if (formAction.indexOf('j') === -1) {
|
|
formAction += '&j';
|
|
}
|
|
|
|
uploadNextFile(formAction);
|
|
});
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initBupUpload);
|
|
} else {
|
|
initBupUpload();
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|