add viewer scripts

This commit is contained in:
2020-01-20 19:55:51 +01:00
parent 129d6915d2
commit 10cbc809ad
22 changed files with 1064 additions and 934 deletions

View File

@@ -1,182 +0,0 @@
/* global Viewer */
var ListNavigator = {
length: 0,
position: 0,
data: [],
history: [],
shuffle: false,
nextItem: function(){
if(!Viewer.isList){
return;
}
if(this.shuffle){
this.randItem();
return;
}
if(this.position >= this.length){
this.position = 0;
}else{
this.position++;
}
this.setItem(this.position);
},
previousItem: function(){
if(!Viewer.isList){
return;
}
if(this.position === 0){
this.position = this.length - 1;
}else{
this.position--;
}
this.setItem(this.position);
},
randItem: function(){
if(!Viewer.isList){
return;
}
// Avoid viewing the same file multiple times
var rand;
do {
rand = Math.round(Math.random() * this.length);
console.log("rand is " + rand);
} while(this.inHistory(rand));
this.setItem(rand);
},
setItem: function(index){
if(index >= this.length){
this.position = 0;
}else{
this.position = index;
}
// Set the URL hash
location.hash = "item=" + this.position;
Viewer.setFile(this.data[this.position]);
this.addToHistory(index);
$("#list_navigator").find("*").removeClass("file_button_selected");
var selectedItem = $("#list_navigator div").eq(this.position);
selectedItem.addClass("file_button_selected");
var itemWidth = selectedItem.outerWidth(true);
// This centers the scroll bar exactly on the selected item
$("#list_navigator").animate(
{scrollLeft: ((this.position * itemWidth) + (itemWidth / 2)) - ($("#list_navigator").width() / 2)},
{duration: 1000, queue: false}
);
this.loadThumbnails(index);
},
addToHistory: function(index){
if(this.history.length >= (this.length - 6)){
this.history.shift();
}
this.history.push(index);
},
inHistory: function(index){
var i = $.inArray(index, this.history); // Returns -1 when the item is not found
return (i !== -1); // Return false when it's not in the array
},
toggleShuffle: function(){
this.shuffle = !this.shuffle; // :P
if(this.shuffle){
$("#btnShuffle > span").html("Shuffle ☑"); // Check icon
$("#btnShuffle").addClass("button_highlight");
}else{
$("#btnShuffle > span").html("Shuffle ☐"); // Empty checkbox
$("#btnShuffle").removeClass("button_highlight");
}
},
loadThumbnails: function(index){
var startPos = +index - 50;
var endPos = +index + 50;
// fyi, the + is to let javascript know it's actually a number instead of a string
if(startPos < 0){
startPos = 0;
}
if(endPos >= this.length){
endPos = this.length - 1;
}
console.log(endPos);
var navigatorItems = document.getElementById("list_navigator").children
for (i = startPos; i <= endPos; i++){
if (navigatorItems[i].innerHTML.includes("list_item_thumbnail")) {
continue; // Thumbnail already loaded
}
var thumb = "/api/file/" + this.data[i].id + "/thumbnail?width=48&height=48";
var name = this.data[i].name;
var itemHtml = "<img src=\"" + thumb + "\" "
+ "class=\"list_item_thumbnail\" alt=\"" + escapeHTML(name) + "\"/>"
+ escapeHTML(name);
navigatorItems[i].innerHTML = itemHtml;
}
},
init: function(data){
this.data = data;
this.length = data.length;
var listHTML = "";
data.forEach(function(item, i){
var filename;
if(item.name !== "null"){
filename = item.name;
}else{
filename = "Removed File";
}
listHTML += "<div class=\"file_button list_item\" "
+ "onClick=\"ListNavigator.setItem('" + i + "')\">"
+ escapeHTML(filename) + "<br>"
+ "</div>";
});
document.getElementById("list_navigator").innerHTML = listHTML;
document.getElementById("btnDownloadList").style.display = "";
document.getElementById("btnShuffle").style.display = "";
// Make the navigator visible
document.getElementById("list_navigator").style.display = "inline-block";
// Skip to the file defined in the link hash
if(Number.isInteger(parseInt(getHashValue("item")))){
this.setItem(parseInt(getHashValue("item")));
}else{
this.setItem(0);
}
}
};
// Misc function, don't really know where else to put it
function getHashValue(key) {
var matches = location.hash.match(new RegExp(key + '=([^&]*)'));
return matches ? matches[1] : null;
}

View File

@@ -1,356 +0,0 @@
/*
* Time for a more Java-like approach.
*
* Feel free to use this of course
*
* Made by Fornax
*/
/* global Viewer */
var Toolbar = {
visible: false,
toggle: function () {
if (this.visible) {
if (Sharebar.visible) {
Sharebar.toggle();
}
document.getElementById("toolbar").style.left = "-8em";
document.getElementById("filepreview").style.left = "0px";
document.getElementById("button_toggle_toolbar").classList.remove("button_highlight");
this.visible = false;
} else {
document.getElementById("toolbar").style.left = "0px";
document.getElementById("filepreview").style.left = "8em";
document.getElementById("button_toggle_toolbar").classList.add("button_highlight");
this.visible = true;
}
},
download: function () {
var triggerDL = function(){
document.getElementById("download_frame").src = "/api/file/" + Viewer.currentFile + "?download";
}
if (captchaKey === "a"){
// If the server doesn't support captcha there's no use in checking
// availability
triggerDL();
return;
}
$.getJSON(
apiEndpoint + "/file/" + Viewer.currentFile + "/availability"
).done(function(data){
if(data.success === true){
// Downloading is allowed, start the download
triggerDL();
}
}).fail(function(data){
if(data.responseJSON.success === false) {
var popupDiv = document.getElementById("captcha_popup");
var popupTitle = document.getElementById("captcha_popup_title");
var popupContent = document.getElementById("captcha_popup_content");
var popupCaptcha = document.getElementById("captcha_popup_captcha");
if(data.responseJSON.value === "file_rate_limited_captcha_required") {
popupTitle.innerText = "Rate limiting enabled!";
popupContent.innerText = "This file is using a suspicious "+
"amount of bandwidth relative to its popularity. To "+
"continue downloading this file you will have to "+
"prove that you're a human first.";
}else if(data.responseJSON.value === "virus_detected_captcha_required"){
popupTitle.innerText = "Malware warning!";
popupContent.innerText = "According to our scanning "+
"systems this file may contain a virus of type '"+
data.responseJSON.extra+"'. You can continue "+
"downloading this file at your own risk, but you will "+
"have to prove that you're a human first.";
}
// Load the recaptcha script with a load function
$.getScript("https://www.google.com/recaptcha/api.js?onload=loadCaptcha&render=explicit");
popupDiv.style.opacity = "1";
popupDiv.style.visibility = "visible";
}else{
// No JSON, try download anyway
triggerDL();
}
});
},
downloadList: function(){
if(!Viewer.isList){
return;
}
document.getElementById("download_frame").src = "/api/list/" + Viewer.listId + "/zip";
},
copyUrl: function () {
if(copyText(window.location.href)) {
console.log('Text copied');
$("#btnCopy>span").text("Copied!");
document.getElementById("btnCopy").classList.add("button_highlight")
} else {
console.log('Copying not supported');
$("#btnCopy>span").text("Error!");
alert("Your browser does not support copying text.");
}
// Return to normal
setTimeout(function(){
$("#btnCopy>span").text("Copy");
document.getElementById("btnCopy").classList.remove("button_highlight")
}, 60000);
},
setStats: function(views, downloads){
document.getElementById("stat_views").innerText = views
document.getElementById("stat_downloads").innerText = Math.round(downloads*10)/10;
}
};
var Sharebar = {
visible: false,
toggle: function(){
if (navigator.share) {
navigator.share({
title: Viewer.title,
text: "Download " + Viewer.title + " here",
url: window.location.href
});
return;
}
if (!Toolbar.visible){
Toolbar.toggle();
}
if(this.visible){
document.getElementById("sharebar").style.left = "-8em";
document.getElementById("btnShare").classList.remove("button_highlight")
this.visible = false;
}else{
document.getElementById("sharebar").style.left = "8em";
document.getElementById("btnShare").classList.add("button_highlight")
this.visible = true;
}
}
};
function copyText(text) {
// Create a textarea to copy the text from
var ta = document.createElement("textarea");
ta.setAttribute("readonly", "readonly")
ta.style.position = "absolute";
ta.style.left = "-9999px";
ta.value = text; // Put the text in the textarea
// Add the textarea to the DOM so it can be seleted by the user
document.body.appendChild(ta);
ta.select(); // Select the contents of the textarea
var success = document.execCommand("copy"); // Copy the selected text
document.body.removeChild(ta); // Remove the textarea
return success;
}
function loadCaptcha(){
grecaptcha.render("captcha_popup_captcha", {
sitekey: captchaKey,
theme: "dark",
callback: function(token){
document.getElementById("download_frame").src = "/api/file/" + Viewer.currentFile +
"?download&recaptcha_response="+token;
setTimeout(function(){
var popupDiv = document.getElementById("captcha_popup");
popupDiv.style.opacity = "0";
popupDiv.style.visibility = "hidden";
}, 1000)
}
});
}
function formatDataVolume(amt) {
if (amt > 1e12) {
return Math.round(amt/1e9)/1e3 + " TB";
} else if (amt > 1e9) {
return Math.round(amt/1e6)/1e3 + " GB";
} else if (amt > 1e6) {
return Math.round(amt/1e3)/1e3 + " MB";
} else if (amt > 1e3) {
return Math.round(amt)/1e3 + " kB";
}
return amt + " B"
}
var DetailsWindow = {
visible: false,
fileID: "",
graph: 0,
popupDiv: document.getElementById("details_popup"),
detailsButton: document.getElementById("btnDetails"),
toggle: function () {
if (this.visible) {
this.popupDiv.style.opacity = "0"
this.popupDiv.style.visibility = "hidden"
this.detailsButton.classList.remove("button_highlight")
this.visible = false;
} else {
this.popupDiv.style.opacity = "1"
this.popupDiv.style.visibility = "visible"
this.detailsButton.classList.add("button_highlight")
this.visible = true;
// This is a workaround for a chrome bug which makes it so hidden
// windows can't be scrolled after they are shown
this.popupDiv.focus();
if (this.graph === 0) {
this.renderGraph();
}
this.updateGraph(this.fileID);
}
},
setDetails: function (file) {
var that = this;
if (Viewer.isList) {
// Lists give incomplete file information, so we have to request
// more details in the background. File descriptions only exist in
// lists, so for that we use the data provided in the page source
$.ajax({
dataType: "json",
url: apiEndpoint + "/file/" + file.id + "/info",
success: function(data){
that.fileID = data.id;
$("#info_file_details").html(
"<table>"
+ "<tr><td>Name<td><td>" + escapeHTML(data.name) + "</td></tr>"
+ "<tr><td>URL<td><td><a href=\"/u/" + data.id + "\">/u/" + data.id + "</a></td></tr>"
+ "<tr><td>Mime Type<td><td>" + escapeHTML(data.mime_type) + "</td></tr>"
+ "<tr><td>ID<td><td>" + data.id + "</td></tr>"
+ "<tr><td>Size<td><td>" + formatDataVolume(data.size) + "</td></tr>"
+ "<tr><td>Bandwidth<td><td>" + formatDataVolume(data.bandwidth_used) + "</td></tr>"
+ "<tr><td>Upload Date<td><td>" + data.date_upload + "</td></tr>"
+ "<tr><td>Description<td><td>" + escapeHTML(file.description) + "</td></tr>"
+ "</table>"
);
Toolbar.setStats(data.views, data.bandwidth_used/data.size);
if(that.visible) {
that.updateGraph(that.fileID);
}
}
});
} else {
this.fileID = file.id;
$("#info_file_details").html(
"<table>"
+ "<tr><td>Name<td><td>" + escapeHTML(file.name) + "</td></tr>"
+ "<tr><td>Mime Type<td><td>" + escapeHTML(file.mime_type) + "</td></tr>"
+ "<tr><td>ID<td><td>" + file.id + "</td></tr>"
+ "<tr><td>Size<td><td>" + formatDataVolume(file.size) + "</td></tr>"
+ "<tr><td>Bandwidth<td><td>" + formatDataVolume(file.bandwidth_used) + "</td></tr>"
+ "<tr><td>Upload Date<td><td>" + file.date_upload + "</td></tr>"
+ "</table>"
);
Toolbar.setStats(file.views, file.bandwidth_used/file.size);
if(this.visible) {
this.updateGraph(file.id);
}
}
},
updateGraph: function(fileID) {
var that = this;
console.log("updating graph "+fileID);
$.get(apiEndpoint+"/file/" + fileID + "/timeseries?interval=60?days=14", function(response){
console.log(response);
if (response.success) {
that.graph.data.labels = response.labels;
that.graph.data.datasets[0].data = response.downloads;
that.graph.data.datasets[1].data = response.views;
that.graph.update();
}
});
},
renderGraph: function() {
console.log("rendering graph");
Chart.defaults.global.defaultFontColor = "#b3b3b3";
Chart.defaults.global.defaultFontSize = 15;
Chart.defaults.global.defaultFontFamily = "Ubuntu";
Chart.defaults.global.aspectRatio = 2.5;
Chart.defaults.global.elements.point.radius = 0;
Chart.defaults.global.tooltips.mode = "index";
Chart.defaults.global.tooltips.axis = "x";
Chart.defaults.global.tooltips.intersect = false;
this.graph = new Chart(
document.getElementById('bandwidth_chart'),
{
type: 'line',
data: {
datasets: [
{
label: "Downloads",
backgroundColor: "rgba(64, 255, 64, .05)",
borderColor: "rgba(128, 255, 128, 1)",
borderWidth: 1.5,
lineTension: 0.1,
fill: true,
yAxisID: "y_bandwidth",
}, {
label: "Views",
backgroundColor: "rgba(64, 64, 255, .1)",
borderColor: "rgba(128, 128, 255, 1)",
borderWidth: 1.5,
lineTension: 0.1,
fill: true,
yAxisID: "y_views",
}
]
},
options: {
scales: {
yAxes: [
{
type: "linear",
display: true,
position: "left",
id: "y_bandwidth",
scaleLabel: {
display: true,
labelString: "Downloads"
},
gridLines: {
color: "rgba(100, 255, 100, .1)"
}
}, {
type: "linear",
display: true,
position: "right",
id: "y_views",
scaleLabel: {
display: true,
labelString: "Views"
},
gridLines: {
color: "rgba(128, 128, 255, .2)"
}
}
],
xAxes: [
{
ticks: {
maxRotation: 20
},
gridLines: {
display: false
}
}
]
}
}
}
);
}
};

View File

@@ -1,147 +0,0 @@
/* global ListNavigator, Toolbar, DetailsWindow */
var Viewer = {
currentFile: "",
title: "", // Contains either the file name or list title
listId: "",
isList: false,
isFile: false,
initialized: false,
init: function(type, data){
if(this.initialized){
return;
}
// On small screens the toolbar takes too much space, so it collapses automatically
if($("#filepreview").width() > 600 && !Toolbar.visible){
Toolbar.toggle();
}
// The close button only works if the window has an opener. So we hide
// the button if it does not
if (window.opener === null && window.history.length !== 1) {
$("#button_close_file_viewer").remove();
}
if(type === "file"){
this.isFile = true;
this.currentFile = data.id;
this.title = data.name;
this.setFile(data);
} else if (type === "list") {
this.isList = true;
this.listId = data.id;
this.title = data.title;
ListNavigator.init(data.data);
}
renderSponsors();
this.initialized = true;
},
setFile: function(file){
this.currentFile = file.id;
var title = "";
if (this.isList) {
document.getElementById("file_viewer_headerbar_title").style.lineHeight = "1em";
document.getElementById("file_viewer_list_title").innerText = this.title;
document.getElementById("file_viewer_file_title").innerText = file.name;
document.title = this.title + " ~ " + file.name + " ~ pixeldrain";
} else {
document.getElementById("file_viewer_file_title").innerText = file.name;
document.title = file.name + " ~ pixeldrain";
}
$.get("/u/" + file.id + "/preview", function(response){
$("#filepreview").html(response);
});
DetailsWindow.setDetails(file);
}
};
// Against XSS attacks
function escapeHTML(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
// Register keyboard shortcuts
document.addEventListener("keydown", function(event){
if (event.ctrlKey || event.altKey) {
return // prevent custom shortcuts from interfering with system shortcuts
}
switch (event.which) {
case 65: // A or left arrow key go to previous file
case 37:
ListNavigator.previousItem();
break;
case 68: // D or right arrow key go to next file
case 39:
ListNavigator.nextItem();
break;
case 83:
if (event.shiftKey) {
Toolbar.downloadList(); // SHIFT + S downloads all files in list
} else {
Toolbar.download(); // S to download the current file
}
break;
case 82: // R to toggle list shuffle
ListNavigator.toggleShuffle();
break;
case 67: // C to copy to clipboard
Toolbar.copyUrl();
break;
case 73: // I to open the details window
DetailsWindow.toggle();
break;
case 81: // Q to close the window
window.close();
break;
}
});
window.addEventListener("resize", renderSponsors);
function renderSponsors() {
var scale = 1;
var scaleWidth = 1;
var scaleHeight = 1;
var minWidth = 728;
var minHeight = 800;
if (window.innerWidth < minWidth) {
scaleWidth = window.innerWidth/minWidth;
}
if (window.innerHeight < minHeight) {
scaleHeight = window.innerHeight/minHeight;
}
scale = scaleWidth < scaleHeight ? scaleWidth : scaleHeight;
console.log(scale, scaleWidth, scaleHeight);
// Because of the scale transformation the automatic margins don't work
// anymore. So we have to maunally calculate the margin. Where we take the
// width of the viewport - the width of the ad to calculate the amount of
// pixels around the ad. We multiply the ad size by the scale we calcualted
// to account for the smaller size.
var offset = (window.innerWidth - (minWidth*scale)) / 2
if (offset < 0) {
offset = 0
}
document.querySelector(".sponsors > iframe").style.marginLeft = offset+"px";
if (scale == 1) {
document.querySelector(".sponsors > iframe").style.transform = "none";
document.querySelector(".sponsors").style.height = "90px";
} else {
document.querySelector(".sponsors > iframe").style.transform = "scale("+scale+")";
document.querySelector(".sponsors").style.height = (scale*90)+"px";
}
}

View File

@@ -66,3 +66,19 @@ function addUploadHistory(fileID) {
// Save the new ID
localStorage.setItem("uploaded_files", fileID + "," + uploads);
}
function copyText(text) {
// Create a textarea to copy the text from
let ta = document.createElement("textarea");
ta.setAttribute("readonly", "readonly")
ta.style.position = "absolute";
ta.style.left = "-9999px";
ta.value = text; // Put the text in the textarea
// Add the textarea to the DOM so it can be seleted by the user
document.body.appendChild(ta);
ta.select() // Select the contents of the textarea
let success = document.execCommand("copy"); // Copy the selected text
document.body.removeChild(ta); // Remove the textarea
return success;
}

View File

@@ -0,0 +1,184 @@
class DetailsWindow {
viewer = null;
visible = false;
fileID = "";
graph = 0;
divPopup = null;
btnDetails = null;
btnCloseDetails = null;
divFileDetails = null;
constructor(viewer) {let dw = this;
dw.viewer = viewer;
dw.divPopup = document.getElementById("details_popup");
dw.btnDetails = document.getElementById("btn_details");
dw.btnCloseDetails = document.getElementById("btn_close_details");
dw.divFileDetails = document.getElementById("info_file_details");
dw.btnDetails.addEventListener("click", () => { dw.toggle(); });
dw.btnCloseDetails.addEventListener("click", () => { dw.toggle(); });
}
toggle() {let dw = this;
if (dw.visible) {
dw.divPopup.style.opacity = "0";
dw.divPopup.style.visibility = "hidden";
dw.btnDetails.classList.remove("button_highlight");
dw.visible = false;
} else {
dw.divPopup.style.opacity = "1";
dw.divPopup.style.visibility = "visible";
dw.btnDetails.classList.add("button_highlight");
dw.visible = true;
// This is a workaround for a chrome bug which makes it so hidden
// windows can't be scrolled after they are shown
dw.divPopup.focus();
if (dw.graph === 0) {
dw.renderGraph();
}
dw.updateGraph(dw.fileID);
}
}
setDetails(file) {let dw = this;
if (dw.viewer.isList) {
// Lists give incomplete file information, so we have to request
// more details in the background. File descriptions only exist in
// lists, so for that we use the data provided in the page source
fetch(apiEndpoint + "/file/" + file.id + "/info").then(resp => {
if (!resp.ok) {return;}
return resp.json();
}).then(resp => {
dw.fileID = resp.id;
dw.divFileDetails.innerHTML = "<table>"
+ "<tr><td>Name<td><td>" + escapeHTML(resp.name) + "</td></tr>"
+ "<tr><td>URL<td><td><a href=\"/u/" + resp.id + "\">/u/" + resp.id + "</a></td></tr>"
+ "<tr><td>Mime Type<td><td>" + escapeHTML(resp.mime_type) + "</td></tr>"
+ "<tr><td>ID<td><td>" + resp.id + "</td></tr>"
+ "<tr><td>Size<td><td>" + formatDataVolume(resp.size) + "</td></tr>"
+ "<tr><td>Bandwidth<td><td>" + formatDataVolume(resp.bandwidth_used) + "</td></tr>"
+ "<tr><td>Upload Date<td><td>" + resp.date_upload + "</td></tr>"
+ "<tr><td>Description<td><td>" + escapeHTML(file.description) + "</td></tr>"
+ "</table>";
dw.viewer.toolbar.setStats(resp.views, resp.bandwidth_used/resp.size);
if(dw.visible) {
dw.updateGraph(dw.fileID);
}
})
} else {
dw.fileID = file.id;
dw.divFileDetails.innerHTML = "<table>"
+ "<tr><td>Name<td><td>" + escapeHTML(file.name) + "</td></tr>"
+ "<tr><td>Mime Type<td><td>" + escapeHTML(file.mime_type) + "</td></tr>"
+ "<tr><td>ID<td><td>" + file.id + "</td></tr>"
+ "<tr><td>Size<td><td>" + formatDataVolume(file.size) + "</td></tr>"
+ "<tr><td>Bandwidth<td><td>" + formatDataVolume(file.bandwidth_used) + "</td></tr>"
+ "<tr><td>Upload Date<td><td>" + file.date_upload + "</td></tr>"
+ "</table>";
dw.viewer.toolbar.setStats(file.views, file.bandwidth_used/file.size);
if(dw.visible) {
dw.updateGraph(file.id);
}
}
}
updateGraph(fileID) {let dw = this;
console.log("updating graph "+fileID);
fetch(apiEndpoint+"/file/" + fileID + "/timeseries?interval=60?days=14").then(resp => {
if (!resp.ok) {return null;}
return resp.json();
}).then(resp => {
dw.graph.data.labels = resp.labels;
dw.graph.data.datasets[0].data = resp.downloads;
dw.graph.data.datasets[1].data = resp.views;
dw.graph.update();
})
}
renderGraph() {let dw = this;
console.log("rendering graph");
Chart.defaults.global.defaultFontColor = "#b3b3b3";
Chart.defaults.global.defaultFontSize = 15;
Chart.defaults.global.defaultFontFamily = "Ubuntu";
Chart.defaults.global.aspectRatio = 2.5;
Chart.defaults.global.elements.point.radius = 0;
Chart.defaults.global.tooltips.mode = "index";
Chart.defaults.global.tooltips.axis = "x";
Chart.defaults.global.tooltips.intersect = false;
dw.graph = new Chart(
document.getElementById('bandwidth_chart'),
{
type: 'line',
data: {
datasets: [
{
label: "Downloads",
backgroundColor: "rgba(64, 255, 64, .05)",
borderColor: "rgba(128, 255, 128, 1)",
borderWidth: 1.5,
lineTension: 0.1,
fill: true,
yAxisID: "y_bandwidth",
}, {
label: "Views",
backgroundColor: "rgba(64, 64, 255, .1)",
borderColor: "rgba(128, 128, 255, 1)",
borderWidth: 1.5,
lineTension: 0.1,
fill: true,
yAxisID: "y_views",
}
]
},
options: {
scales: {
yAxes: [
{
type: "linear",
display: true,
position: "left",
id: "y_bandwidth",
scaleLabel: {
display: true,
labelString: "Downloads"
},
gridLines: {
color: "rgba(100, 255, 100, .1)"
}
}, {
type: "linear",
display: true,
position: "right",
id: "y_views",
scaleLabel: {
display: true,
labelString: "Views"
},
gridLines: {
color: "rgba(128, 128, 255, .2)"
}
}
],
xAxes: [
{
ticks: {
maxRotation: 20
},
gridLines: {
display: false
}
}
]
}
}
}
);
}
}

View File

@@ -0,0 +1,193 @@
class ListNavigator {
viewer = null;
length = 0;
position = 0;
data = [];
history = [];
shuffle = false;
divListNavigator = null;
btnDownloadList = null;
btnShuffle = null;
constructor(viewer, data){let ln = this;
ln.viewer = viewer;
ln.data = data;
ln.length = data.length;
ln.divListNavigator = document.getElementById("list_navigator");
ln.btnDownloadList = document.getElementById("btn_download_list");
ln.btnDownloadList.style.display = "";
ln.btnDownloadList.addEventListener("click", () => { ln.downloadList(); });
ln.btnShuffle = document.getElementById("btn_shuffle");
ln.btnShuffle.style.display = "";
ln.btnShuffle.addEventListener("click", () => { ln.toggleShuffle(); });
// Render list contents in list navigator div
data.forEach((item, i) => {
let filename;
if(item.name !== "null"){
filename = item.name;
}else{
filename = "Removed File";
}
let d = document.createElement("div");
d.classList = "file_button list_item";
d.addEventListener("click", () => { ln.setItem(i); });
d.innerText = filename;
ln.divListNavigator.appendChild(d);
});
// Make the navigator visible
ln.divListNavigator.style.display = "inline-block";
// Skip to the file defined in the link hash
if(Number.isInteger(parseInt(getHashValue("item")))){
ln.setItem(parseInt(getHashValue("item")));
}else{
ln.setItem(0);
}
}
nextItem(){let ln = this;
if(ln.shuffle){
ln.randItem();
return;
}
if (ln.position >= ln.length) {
ln.position = 0;
} else {
ln.position++;
}
ln.setItem(ln.position);
}
previousItem(){let ln = this;
if(ln.position === 0){
ln.position = ln.length - 1;
}else{
ln.position--;
}
ln.setItem(ln.position);
}
randItem(){let ln = this;
// Avoid viewing the same file multiple times
let rand;
do {
rand = Math.round(Math.random() * ln.length);
console.log("rand is " + rand);
} while(ln.history.indexOf(index) > -1);
ln.setItem(rand);
}
setItem(index){let ln = this;
if(index >= ln.length){
ln.position = 0;
}else{
ln.position = index;
}
// Set the URL hash
location.hash = "item=" + ln.position;
ln.viewer.setFile(ln.data[ln.position]);
ln.addToHistory(index);
ln.loadThumbnails(index);
document.querySelectorAll("#list_navigator > .file_button_selected").forEach(el => {
el.classList.remove("file_button_selected");
});
let selectedItem = ln.divListNavigator.children[ln.position];
selectedItem.classList.add("file_button_selected");
let cst = window.getComputedStyle(selectedItem);
let itemWidth = selectedItem.offsetWidth + parseInt(cst.marginLeft) + parseInt(cst.marginRight);
let start = ln.divListNavigator.scrollLeft;
let end = ((ln.position * itemWidth) + (itemWidth / 2)) - (ln.divListNavigator.clientWidth / 2);
let steps = 60; // One second
let stepSize = (end - start)/steps;
let animateScroll = (pos, step) => {
ln.divListNavigator.scrollLeft = pos;
if (step < steps) {
requestAnimationFrame(() => {
animateScroll(pos+stepSize, step+1);
});
}
};
animateScroll(start, 0);
}
downloadList(){let ln = this;
document.getElementById("download_frame").src = "/api/list/" + ln.viewer.listId + "/zip";
}
addToHistory(index){let ln = this;
if(ln.history.length >= (ln.length - 6)){
ln.history.shift();
}
ln.history.push(index);
}
toggleShuffle(){let ln = this;
ln.shuffle = !ln.shuffle; // :P
if(ln.shuffle){
document.querySelector("#btn_shuffle > span").innerHTML = "Shuffle&nbsp;&#x2611;"; // Check icon
ln.btnShuffle.classList.add("button_highlight");
}else{
document.querySelector("#btn_shuffle > span").innerHTML = "Shuffle&nbsp;&#x2610;"; // Empty checkbox
ln.btnShuffle.classList.remove("button_highlight");
}
}
loadThumbnails(index){let ln = this;
let startPos = +index - 50;
let endPos = +index + 50;
// fyi, the + is to let javascript know it's actually a number instead of a string
if(startPos < 0){
startPos = 0;
}
if(endPos >= ln.length){
endPos = ln.length - 1;
}
let navigatorItems = document.getElementById("list_navigator").children
for (let i = startPos; i <= endPos; i++){
if (navigatorItems[i].innerHTML.includes("list_item_thumbnail")) {
continue; // Thumbnail already loaded
}
let thumb = "/api/file/" + ln.data[i].id + "/thumbnail?width=48&height=48";
let name = ln.data[i].name;
let itemHtml = "<img src=\"" + thumb + "\" "
+ "class=\"list_item_thumbnail\" alt=\"" + escapeHTML(name) + "\"/>"
+ escapeHTML(name);
navigatorItems[i].innerHTML = itemHtml;
}
}
};
// Misc function, don't really know where else to put it
function getHashValue(key) {
let matches = location.hash.match(new RegExp(key + '=([^&]*)'));
return matches ? matches[1] : null;
}

View File

@@ -0,0 +1,186 @@
class Toolbar {
viewer = null;
visible = false;
sharebarVisible = false;
// Elements
divToolbar = null;
divFilePreview = null;
downloadFrame = null
spanViews = null;
spanDownloads = null;
btnToggleToolbar = null;
btnDownload = null;
btnCopyLink = null;
spanCopyLink = null;
btnShare = null;
divSharebar = null;
constructor(viewer) {let t = this;
this.viewer = viewer;
t.divToolbar = document.getElementById("toolbar");
t.divFilePreview = document.getElementById("filepreview");
t.downloadFrame = document.getElementById("download_frame");
t.spanViews = document.getElementById("stat_views");
t.spanDownloads = document.getElementById("stat_downloads");
t.btnToggleToolbar = document.getElementById("btn_toggle_toolbar");
t.btnDownload = document.getElementById("btn_download");
t.btnCopyLink = document.getElementById("btn_copy");
t.spanCopyLink = document.querySelector("#btn_copy > span");
t.btnShare = document.getElementById("btn_share");
t.divSharebar = document.getElementById("sharebar");
t.btnToggleToolbar.addEventListener("click", () => { t.toggle(); });
t.btnDownload.addEventListener("click", () => { t.download(); });
t.btnCopyLink.addEventListener("click", () => { t.copyUrl(); });
t.btnShare.addEventListener("click", () => { t.toggleSharebar(); });
}
toggle() {let t = this;
if (t.visible) {
if (t.sharebarVisible) { t.toggleSharebar(); }
t.divToolbar.style.left = "-8em";
t.divFilePreview.style.left = "0px";
t.btnToggleToolbar.classList.remove("button_highlight");
t.visible = false;
} else {
t.divToolbar.style.left = "0px";
t.divFilePreview.style.left = "8em";
t.btnToggleToolbar.classList.add("button_highlight");
t.visible = true;
}
}
toggleSharebar(){let t = this;
if (navigator.share) {
navigator.share({
title: t.viewer.title,
text: "Download " + t.viewer.title + " here",
url: window.location.href
});
return;
}
if(t.sharebarVisible){
t.divSharebar.style.left = "-8em";
t.btnShare.classList.remove("button_highlight")
t.sharebarVisible = false;
}else{
t.divSharebar.style.left = "8em";
t.btnShare.classList.add("button_highlight")
t.sharebarVisible = true;
}
}
download() {let t = this;
let triggerDL = function(){
t.downloadFrame.src = apiEndpoint+"/file/"+t.viewer.currentFile+"?download";
}
if (captchaKey === "a"){
// If the server doesn't support captcha there's no use in checking
// availability
triggerDL();
return;
}
fetch(apiEndpoint+"/file/"+t.viewer.currentFile+"/availability").then(resp => {
return resp.json();
}).then(resp => {
let popupDiv = document.getElementById("captcha_popup");
let popupTitle = document.getElementById("captcha_popup_title");
let popupContent = document.getElementById("captcha_popup_content");
let showCaptcha = function() {
// Load the recaptcha script with a load function
var script = document.createElement("script");
script.src = "https://www.google.com/recaptcha/api.js?onload=loadCaptcha&render=explicit";
document.appendChild(script);
// $.getScript("https://www.google.com/recaptcha/api.js?onload=loadCaptcha&render=explicit");
popupDiv.style.opacity = "1";
popupDiv.style.visibility = "visible";
}
if (resp.value === "file_rate_limited_captcha_required") {
popupTitle.innerText = "Rate limiting enabled!";
popupContent.innerText = "This file is using a suspicious "+
"amount of bandwidth relative to its popularity. To "+
"continue downloading this file you will have to "+
"prove that you're a human first.";
showCaptcha();
} else if (resp.value === "virus_detected_captcha_required") {
popupTitle.innerText = "Malware warning!";
popupContent.innerText = "According to our scanning "+
"systems this file may contain a virus of type '"+
resp.extra+"'. You can continue downloading this file at "+
"your own risk, but you will have to prove that you're a "+
"human first.";
showCaptcha();
} else {
triggerDL();
}
}).catch(e => {
triggerDL();
});
}
copyUrl() {let t = this;
if(copyText(window.location.href)) {
console.log('Text copied');
t.spanCopyLink.innerText = "Copied!";
t.btnCopyLink.classList.add("button_highlight")
} else {
console.log('Copying not supported');
t.spanCopyLink.innerText = "Error!";
alert("Your browser does not support copying text.");
}
// Return to normal
setTimeout(function(){
t.spanCopyLink.innerText = "Copy";
t.btnCopyLink.classList.remove("button_highlight")
}, 60000);
}
setStats(views, downloads) {let t = this;
t.spanViews.innerText = views
t.spanDownloads.innerText = Math.round(downloads*10)/10;
}
}
// Called by the google recaptcha script
function loadCaptcha(){
grecaptcha.render("captcha_popup_captcha", {
sitekey: captchaKey,
theme: "dark",
callback: function(token){
document.getElementById("download_frame").src = "/api/file/" + Viewer.currentFile +
"?download&recaptcha_response="+token;
setTimeout(function(){
let popupDiv = document.getElementById("captcha_popup");
popupDiv.style.opacity = "0";
popupDiv.style.visibility = "hidden";
}, 1000)
}
});
}
function formatDataVolume(amt) {
if (amt > 1e12) {
return Math.round(amt/1e9)/1e3 + " TB";
} else if (amt > 1e9) {
return Math.round(amt/1e6)/1e3 + " GB";
} else if (amt > 1e6) {
return Math.round(amt/1e3)/1e3 + " MB";
} else if (amt > 1e3) {
return Math.round(amt)/1e3 + " kB";
}
return amt + " B"
}

View File

@@ -0,0 +1,189 @@
class Viewer {
// Child components
toolbar = null;
listNavigator = null;
detailsWindow = null;
divFilepreview = null;
currentFile = "";
title = ""; // Contains either the file name or list title
listId = "";
isList = false;
isFile = false;
initialized = false;
constructor(type, data) {let v = this;
if(v.initialized){
return;
}
v.toolbar = new Toolbar(v);
v.detailsWindow = new DetailsWindow(v);
v.divFilepreview = document.getElementById("filepreview");
// On small screens the toolbar takes too much space, so it collapses
// automatically
if(v.divFilepreview.clientWidth > 600 && !v.toolbar.visible){
v.toolbar.toggle();
}
// The close button only works if the window has an opener. So we hide
// the button if it does not
if (window.opener === null && window.history.length !== 1) {
document.getElementById("button_close_file_viewer").remove()
}
if(type === "file"){
v.isFile = true;
v.currentFile = data.id;
v.title = data.name;
v.setFile(data);
} else if (type === "list") {
v.isList = true;
v.listId = data.id;
v.title = data.title;
v.listNavigator = new ListNavigator(v, data.data);
}
v.renderSponsors();
window.addEventListener("resize", e => { v.renderSponsors(e); });
// Register keyboard shortcuts
document.addEventListener("keydown", e => { v.keyboardEvent(e); });
v.initialized = true;
}
setFile(file) {let v = this;
v.currentFile = file.id;
if (v.isList) {
document.getElementById("file_viewer_headerbar_title").style.lineHeight = "1em";
document.getElementById("file_viewer_list_title").innerText = this.title;
document.getElementById("file_viewer_file_title").innerText = file.name;
document.title = v.title + " ~ " + file.name + " ~ pixeldrain";
} else {
document.getElementById("file_viewer_file_title").innerText = file.name;
document.title = file.name + " ~ pixeldrain";
}
// Update the file details
v.detailsWindow.setDetails(file);
// Clear the canvas
v.divFilepreview.innerHTML = "";
if (file.mime_type.startsWith("image")) {
new ImageViewer(v, file).render(v.divFilepreview);
} else if (file.mime_type.startsWith("video")) {
new VideoViewer(v, file, () => {
if (v.listNavigator !== null) {
v.listNavigator.nextItem();
}
}).render(v.divFilepreview);
} else if (file.mime_type.startsWith("audio") || file.mime_type === "application/ogg") {
new AudioViewer(v, file, () => {
if (v.listNavigator !== null) {
v.listNavigator.nextItem();
}
}).render(v.divFilepreview);
} else {
fetch("/u/"+file.id+"/preview").then(resp => {
if (!resp.ok) { return Promise.reject(resp.status); }
return resp.text();
}).then(resp => {
v.divFilepreview.innerHTML = resp;
}).catch(err => {
v.divFilepreview.innerText = "Error loading file: "+err;
});
}
}
renderSponsors() {
let scale = 1;
let scaleWidth = 1;
let scaleHeight = 1;
let minWidth = 728;
let minHeight = 800;
if (window.innerWidth < minWidth) {
scaleWidth = window.innerWidth/minWidth;
}
if (window.innerHeight < minHeight) {
scaleHeight = window.innerHeight/minHeight;
}
scale = scaleWidth < scaleHeight ? scaleWidth : scaleHeight;
// Because of the scale transformation the automatic margins don't work
// anymore. So we have to maunally calculate the margin. Where we take the
// width of the viewport - the width of the ad to calculate the amount of
// pixels around the ad. We multiply the ad size by the scale we calcualted
// to account for the smaller size.
let offset = (window.innerWidth - (minWidth*scale)) / 2
if (offset < 0) {
offset = 0
}
document.querySelector(".sponsors > iframe").style.marginLeft = offset+"px";
if (scale == 1) {
document.querySelector(".sponsors > iframe").style.transform = "none";
document.querySelector(".sponsors").style.height = "90px";
} else {
document.querySelector(".sponsors > iframe").style.transform = "scale("+scale+")";
document.querySelector(".sponsors").style.height = (scale*90)+"px";
}
}
keyboardEvent(evt) {let v = this;
if (evt.ctrlKey || evt.altKey) {
return // prevent custom shortcuts from interfering with system shortcuts
}
switch (evt.which) {
case 65: // A or left arrow key go to previous file
case 37:
if (v.listNavigator != null) {
v.listNavigator.previousItem();
}
break;
case 68: // D or right arrow key go to next file
case 39:
if (v.listNavigator != null) {
v.listNavigator.nextItem();
}
break;
case 83:
if (evt.shiftKey) {
v.toolbar.downloadList(); // SHIFT + S downloads all files in list
} else {
v.toolbar.download(); // S to download the current file
}
break;
case 82: // R to toggle list shuffle
if (v.listNavigator != null) {
v.listNavigator.toggleShuffle();
}
break;
case 67: // C to copy to clipboard
v.toolbar.copyUrl();
break;
case 73: // I to open the details window
v.detailsWindow.toggle();
break;
case 81: // Q to close the window
window.close();
break;
}
}
};
// Against XSS attacks
function escapeHTML(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}

View File

@@ -0,0 +1,44 @@
class AudioViewer {
viewer = null;
file = null;
next = null;
container = null;
icon = null;
element = null;
source = null;
constructor(viewer, file, next) {let av = this;
av.viewer = viewer;
av.file = file;
av.next = next;
av.container = document.createElement("div");
av.container.classList = "image-container";
av.container.appendChild(document.createElement("br"));
av.icon = document.createElement("img");
av.icon.src = "/res/img/mime/audio.png";
av.container.appendChild(av.icon);
av.container.appendChild(document.createElement("br"));
av.container.appendChild(document.createTextNode(file.name));
av.container.appendChild(document.createElement("br"));
av.container.appendChild(document.createElement("br"));
av.element = document.createElement("audio");
av.element.autoplay = "autoplay";
av.element.controls = "controls";
av.element.style.width = "90%";
av.element.addEventListener("ended", () => { av.next(); }, false);
av.source = document.createElement("source");
av.source.src = apiEndpoint+"/file/"+av.file.id;
av.element.appendChild(av.source);
av.container.appendChild(av.element);
}
render(parent) {let av = this;
parent.appendChild(av.container);
}
}

View File

@@ -0,0 +1,156 @@
class ImageViewer {
viewer = null;
file = null;
imgContainer = null;
imgElement = null;
zoomed = false;
x = 0;
y = 0;
dragging = false;
constructor(viewer, file) {let iv = this;
iv.viewer = viewer;
iv.file = file;
iv.imgContainer = document.createElement("div");
iv.imgContainer.classList = "image-container";
iv.imgElement = document.createElement("img");
iv.imgElement.classList = "pannable drop-shadow";
iv.imgElement.src = apiEndpoint+"/file/"+iv.file.id;
iv.imgElement.addEventListener("mousedown", (e) => { return iv.mousedown(e); });
iv.imgElement.addEventListener("dblclick", (e) => { return iv.doubleclick(e); });
iv.imgElement.addEventListener("doubletap", (e) => { return iv.doubleclick(e); });
document.addEventListener("mousemove", (e) => { return iv.mousemove(e); });
document.addEventListener("mouseup", (e) => { return iv.mouseup(e); });
iv.imgContainer.appendChild(iv.imgElement);
}
render(parent) {let iv = this;
parent.appendChild(iv.imgContainer);
}
doubleclick(e) {let iv = this;
if (iv.zoomed) {
iv.imgElement.style.maxWidth = "100%";
iv.imgElement.style.maxHeight = "100%";
iv.imgElement.style.top = "50%";
iv.imgElement.style.left = "auto";
iv.imgElement.style.transform = "translateY(-50%)";
iv.imgContainer.style.overflow = "hidden";
iv.zoomed = false;
} else {
iv.imgElement.style.maxWidth = "none";
iv.imgElement.style.maxHeight = "none";
iv.imgElement.style.top = "0";
iv.imgElement.style.left = "";
iv.imgElement.style.transform = "none";
iv.imgContainer.style.overflow = "scroll";
iv.zoomed = true;
}
e.preventDefault();
e.stopPropagation();
return false;
}
mousedown(e) {let iv = this;
if (!iv.dragging && e.which === 1 && iv.zoomed) {
iv.x = e.pageX;
iv.y = e.pageY;
iv.dragging = true;
e.preventDefault();
e.stopPropagation();
return false;
}
}
mousemove(e) {let iv = this;
if (iv.dragging) {
iv.imgContainer.scrollLeft = iv.imgContainer.scrollLeft - (e.pageX - iv.x);
iv.imgContainer.scrollTop = iv.imgContainer.scrollTop - (e.pageY - iv.y);
iv.x = e.pageX;
iv.y = e.pageY;
e.preventDefault();
e.stopPropagation();
return false;
}
}
mouseup(e) {let iv = this;
if (iv.dragging) {
iv.dragging = false;
e.preventDefault();
e.stopPropagation();
return false;
}
}
}
// var zoomed = false;
// // When a user clicks the image
// $("#displayImg").on("dblclick doubletap", function (event) {
// if (zoomed) {
// $("#displayImg").css("max-width", "100%");
// $("#displayImg").css("max-height", "100%");
// $("#displayImg").css("top", "50%");
// $("#displayImg").css("left", "auto");
// $("#displayImg").css("transform", "translateY(-50%)");
// $(".image-container").css("overflow", "hidden");
// zoomed = false;
// } else {
// $("#displayImg").css("max-width", "none");
// $("#displayImg").css("max-height", "none");
// $("#displayImg").css("transform", "none");
// $(".pannable").css("top", "0");
// $(".image-container").css("overflow", "scroll");
// zoomed = true;
// }
// return false;
// });
// Image dragging around the screen
// var drag = {
// x: 0,
// y: 0,
// state: false
// };
// $(".pannable").on("mousedown", function (e) {
// if (!drag.state && e.which === 1 && zoomed) {
// drag.x = e.pageX;
// drag.y = e.pageY;
// drag.state = true;
// return false;
// }
// });
// var img = $(".image-container");
// $(document).on("mousemove", function (e) {
// if (drag.state) {
// img.scrollLeft(img.scrollLeft() - (e.pageX - drag.x));
// img.scrollTop(img.scrollTop() - (e.pageY - drag.y));
// drag.x = e.pageX;
// drag.y = e.pageY;
// }
// });
// $(document).on("mouseup", function () {
// if (drag.state) {
// drag.state = false;
// }
// });

View File

@@ -0,0 +1,34 @@
class VideoViewer {
viewer = null;
file = null;
next = null;
vidContainer = null;
vidElement = null;
videoSource = null;
constructor(viewer, file, next) {let vv = this;
vv.viewer = viewer;
vv.file = file;
vv.next = next;
vv.vidContainer = document.createElement("div");
vv.vidContainer.classList = "image-container";
vv.vidElement = document.createElement("video");
vv.vidElement.autoplay = "autoplay";
vv.vidElement.controls = "controls";
vv.vidElement.classList = "center drop-shadow";
vv.vidElement.addEventListener("ended", () => { vv.next(); }, false);
vv.videoSource = document.createElement("source");
vv.videoSource.src = apiEndpoint+"/file/"+vv.file.id;
vv.vidElement.appendChild(vv.videoSource);
vv.vidContainer.appendChild(vv.vidElement);
}
render(parent) {let vv = this;
parent.appendChild(vv.vidContainer);
}
}

View File

@@ -178,22 +178,6 @@ function copyLink() {
}
}
function copyText(text) {
// Create a textarea to copy the text from
let ta = document.createElement("textarea");
ta.setAttribute("readonly", "readonly")
ta.style.position = "absolute";
ta.style.left = "-9999px";
ta.value = text; // Put the text in the textarea
// Add the textarea to the DOM so it can be seleted by the user
document.body.appendChild(ta);
ta.select() // Select the contents of the textarea
let success = document.execCommand("copy"); // Copy the selected text
document.body.removeChild(ta); // Remove the textarea
return success;
}
/*
* Upload Handlers
*/
@@ -344,6 +328,32 @@ btnCopyBBCode.addEventListener("click", function(){
}
});
let btnCopyMarkdown = document.getElementById("btn_copy_markdown");
btnCopyMarkdown.addEventListener("click", function(){
let text = "";
let uploads = uploader.finishedUploads();
// Add the text to the textarea
for (let i = 0; i < uploads.length; i++) {
// Example: * [Some_file.png](https://pixeldrain.com/u/abcd1234)
if (uploads.length > 1) { text += " * "; }
text += "["+uploads[i].fileName+"]("+domainURL()+"/u/"+uploads[i].fileID+")\n";
}
if (shareLink.includes("/l/")) {
text += " * [All "+uploads.length+" files]("+shareLink+")\n";
}
// Copy the selected text
if(copyText(text)){
btnCopyMarkdown.classList.add("button_highlight");
btnCopyMarkdown.innerHTML = "Markdown copied to clipboard!"
}else{
btnCopyMarkdown.classList.add("button_red");
btnCopyMarkdown.innerHTML = "Copying links failed"
}
});
/*
* Keyboard shortcuts

View File

@@ -347,6 +347,7 @@ pre{
white-space: normal;
text-overflow: ellipsis;
vertical-align: top;
cursor: pointer;
}
.file_button:hover,
.file_button_selected {