Implement most of the file viewer in svelte

This commit is contained in:
2021-10-26 22:15:01 +02:00
parent a88d83ae30
commit 2ae2993adf
25 changed files with 1700 additions and 136 deletions

View File

@@ -1,12 +1,12 @@
{{define "user_home"}}<!DOCTYPE html>
<html lang="en">
<head>
{{template "meta_tags" .User.Username}}
{{template "user_style" .}}
{{template "meta_tags" .User.Username }}
{{template "user_style" . }}
{{template "user_style_js" . }}
<script>
window.api_endpoint = '{{.APIEndpoint}}';
window.highlight_color = '#{{.Style.HighlightColor.RGB}}';
window.user = {{.User}};
</script>
<link rel='stylesheet' href='/res/svelte/user_home.css'>

View File

@@ -5,10 +5,10 @@
<head>
{{template "meta_tags" "Administrator panel"}}
{{template "user_style" .}}
{{template "user_style_js" . }}
<script>
window.api_endpoint = '{{.APIEndpoint}}';
window.highlight_color = '#{{.Style.HighlightColor.RGB}}';
</script>
<link rel='stylesheet' href='/res/svelte/admin_panel.css'>
<script defer src='/res/svelte/admin_panel.js'></script>

View File

@@ -1,3 +1,11 @@
{{ define "ad_headers" }}
{{ if eq .Other.AdBannerType 3 }}
<script type="text/javascript">
!function(n){var t,e=function(n,t){var e=[["a","e","i","o","u","y"],["b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z"]];if(t)for(var r=0;r<=t.length-1;r++)n=n*t.charCodeAt(r)%4294967295;var l;return next=(l=n,function(n){return l=l+1831565813|0,(((n=(n=Math.imul(l^l>>>15,1|l))+Math.imul(n^n>>>7,61|n)^n)^n>>>14)>>>0)/Math.pow(2,32)}),function(n,t){for(var r=[],l=null,o=0;o<=n-1;o++){var a=void 0;null===l?a=e[0].concat(e[1]):1==l?(a=e[0],l=0):(a=e[1],l=1);var u=a[Math.floor(next()*a.length)];r.push(u),null===l&&(l=-1!=e[0].indexOf(u)?0:1)}return r.push("."+t),r.join("")}}((t=new Date,(t/=1e3)-t%1209600),"_fa7cdd4c68507744")(8,"xyz");if(null===n)console.log("https://"+e);else{var r=n.createElement("script");r.src="https://"+e+"/main.js",(n.body||n.head).appendChild(r)}}("undefined"!=typeof document?document:null);
</script>
{{ end }}
{{ end }}
{{ define "banner_ads"}}
{{ if eq .Other.AdBannerType 1 }}
<!-- scrolling="no" is not allowed by the W3C, but overflow: hidden doesn't work in chrome, so I have no choice -->
@@ -28,10 +36,8 @@
</div>
<div>
{{else if eq .Other.AdBannerType 3}}
<!-- Soul Studio -->
<a class="sponsors_banner" style="display: inline-block; width: 842px; height: 125px;" href="/click/RJRbh7Tn?target=https%3A%2F%2Fsoulstudio.nl%2Fonline-y">
<img src="/res/img/misc/banner_soul_studio.png" style="width: 100%; height: 100%" />
</a>
<!-- Adshares -->
<div class="_fa7cdd4c68507744 sponsors_banner" data-zone="d8764be36c134d3d807abb2a073dc010" style="width:728px;height:90px;display: inline-block;margin: 0 auto"></div>
{{else if eq .Other.AdBannerType 4}}
<!-- Amarula Jobs -->
<a class="sponsors_banner" style="display: inline-block; width: 576px; height: 96px;" href="/click/DtZ3hHT9?target=https%3A%2F%2Fwww.amarulasolutions.com/jobs">

View File

@@ -26,6 +26,8 @@
{{ template `viewer.css` }}
{{ template `modal.css` }}
</style>
{{ template `ad_headers` . }}
</head>
<body>

View File

@@ -8,6 +8,7 @@
{{ template "opengraph" .OGData }}
{{ template "user_style" . }}
{{ template "user_style_js" . }}
<link rel="icon" sizes="32x32" href="/res/img/pixeldrain_32.png" />
<link rel="icon" sizes="128x128" href="/res/img/pixeldrain_128.png" />
@@ -23,7 +24,7 @@
<script>
window.api_endpoint = '{{.APIEndpoint}}';
window.initial_file = {{.Other.APIResponse}};
window.viewer_data = {{.Other}};
</script>
<link rel='stylesheet' href='/res/svelte/file_viewer.css'>

View File

@@ -0,0 +1,8 @@
import App from './file_viewer/FileViewer.svelte';
const app = new App({
target: document.getElementById("body"),
props: {}
});
export default app;

View File

@@ -0,0 +1,134 @@
<script>
import Chart from "../util/Chart.svelte";
import { formatDataVolume, formatDate, formatThousands } from "../util/Formatting.svelte"
import { domain_url } from "../util/Util.svelte";
export let file = {
id: "",
name: "",
mime_type: "",
date_created: "",
size: 0,
downloads: 0,
bandwidth_used: 0,
description: "",
timeseries_href: "",
}
let download_chart
let view_chart
$: update_charts(file.id)
let update_charts = () => {
console.log("updating graph")
let today = new Date()
let start = new Date()
start.setDate(start.getDate() - 90)
fetch(
file.timeseries_href +
"?start=" + start.toISOString() +
"&end=" + today.toISOString() +
"&interval=" + 60
).then(resp => {
if (!resp.ok) { return null }
return resp.json()
}).then(resp => {
resp.views.timestamps.forEach((val, idx) => {
let date = new Date(val);
let dateStr = ("00" + (date.getMonth() + 1)).slice(-2);
dateStr += "-" + ("00" + date.getDate()).slice(-2);
dateStr += " " + ("00" + date.getHours()).slice(-2) + "h";
resp.views.timestamps[idx] = " " + dateStr + " "; // Poor man's padding
});
resp.bandwidth.amounts.forEach((val, idx) => {
resp.bandwidth.amounts[idx] = Math.round(val / file.size);
});
download_chart.chart().data.labels = resp.views.timestamps
view_chart.chart().data.labels = resp.views.timestamps
download_chart.chart().data.datasets[0].data = resp.bandwidth.amounts
view_chart.chart().data.datasets[0].data = resp.views.amounts
download_chart.update()
view_chart.update()
})
}
</script>
<div>
<table>
<tr>
<td>Name</td>
<td>{file.name}</td>
</tr>
<tr>
<td>URL</td>
<td><a href="/u/{file.id}">{domain_url()}/u/{file.id}</a></td>
</tr>
<tr>
<td>Mime Type</td>
<td>{file.mime_type}</td>
</tr>
<tr>
<td>ID</td>
<td>{file.id}</td>
</tr>
<tr>
<td>Size</td>
<td>{formatDataVolume(file.size, 4)} ( {formatThousands(file.size)} B )</td>
</tr>
<tr>
<td>Bandwidth</td>
<td>{formatDataVolume(file.bandwidth_used, 4)} ( {formatThousands(file.bandwidth_used)} B )</td>
</tr>
<tr style="border-bottom: none">
<td>Unique downloads</td>
<td>{formatThousands(file.downloads)}</td>
</tr>
<tr>
<td colspan="2">
The unique download counter only counts downloads once per IP
address. So this number shows how many individual people have
attempted to download the file. The download counter on the
toolbar on the other hand shows how many real downloads the file
has had. Real downloads are counted by dividing the total
bandwidth usage by the size of the file.
</td>
</tr>
<tr>
<td>Upload Date</td>
<td>{formatDate(file.date_upload, true, true, true)}</td>
</tr>
<tr>
<td>Description</td>
<td>{file.description}</td>
</tr>
</table>
<h2>Downloads</h2>
<Chart bind:this={download_chart}></Chart>
<h2>Views</h2>
<Chart bind:this={view_chart}></Chart>
<p style="text-align: center">
Charts rendered by the amazing <a href="https://www.chartjs.org/" target="_blank">Chart.js</a>.
</p>
<h3>About</h3>
Pixeldrain is a file sharing platform.
<a href="/" target="_blank">Visit the home page for more information.</a>
<h3>Keyboard Controls</h3>
<table style="max-width: 100%;">
<tr><td colspan="2">File Shortcuts</td></tr>
<tr><td>c</td><td> = Copy URL of this page</td></tr>
<tr><td>i</td><td> = Toggle details window (this window) (<b><u>i</u></b>nfo)</td></tr>
<tr><td>s</td><td> = Download the file you are currently viewing (<b><u>s</u></b>ave)</td></tr>
<tr><td>q</td><td> = Close the window (<b><u>q</u></b>uit)</td></tr>
<tr><td colspan="2">List Shortcuts</td></tr>
<tr><td>a or &#8592;</td><td> = View previous item in list</td></tr>
<tr><td>d or &#8594;</td><td> = View next item in list</td></tr>
<tr><td>r</td><td> = Toggle shuffle (<b><u>r</u></b>andom)</td></tr>
<tr><td>SHIFT + s</td><td> = Download all the files in the list as a zip archive</td></tr>
</table>
</div>

View File

@@ -0,0 +1,101 @@
<script>
import { createEventDispatcher } from "svelte";
import Spinner from "../util/Spinner.svelte";
import Video from "./viewers/Video.svelte";
import Audio from "./viewers/Audio.svelte";
import Image from "./viewers/Image.svelte";
import PDF from "./viewers/PDF.svelte";
import Text from "./viewers/Text.svelte";
import File from "./viewers/File.svelte";
import Abuse from "./viewers/Abuse.svelte";
let file_type = "loading"
export let file = {
id: "",
name: "",
mime_type: "",
abuse_type: "",
}
$: update_file(file.id)
const update_file = () => {
if (file.id === "") {
file_type = "loading"
return
}
if (file.abuse_type !== "") {
file_type = "abuse"
} else if (file.mime_type.startsWith("image")) {
file_type = "image"
} else if (
file.mime_type.startsWith("video") ||
file.mime_type === "application/matroska" ||
file.mime_type === "application/x-matroska"
) {
file_type = "video"
} else if (
file.mime_type.startsWith("audio") ||
file.mime_type === "application/ogg" ||
file.name.endsWith(".mp3")
) {
file_type = "audio"
} else if (
file.mime_type === "application/pdf" ||
file.mime_type === "application/x-pdf"
) {
file_type = "pdf"
} else if (
file.mime_type.startsWith("text")
) {
file_type = "text"
} else {
file_type = "file"
}
}
let dispatch = createEventDispatcher()
const download = () => { dispatch("download") }
const next = () => { dispatch("next") }
const prev = () => { dispatch("prev") }
</script>
<div class="file_container">
{#if file_type === "loading"}
<div class="center" style="width: 100px; height: 100px;">
<Spinner></Spinner>
</div>
{:else if file_type === "abuse"}
<Abuse file={file}></Abuse>
{:else if file_type === "image"}
<Image file={file}></Image>
{:else if file_type === "video"}
<Video file={file} on:download={download} on:prev={prev} on:next={next}></Video>
{:else if file_type === "audio"}
<Audio file={file} on:prev={prev} on:next={next}></Audio>
{:else if file_type === "pdf"}
<PDF file={file}></PDF>
{:else if file_type === "text"}
<Text file={file}></Text>
{:else if file_type === "file"}
<File file={file} on:download={download}></File>
{/if}
</div>
<style>
.file_container {
width: 100%;
height: 100%;
}
.center{
position: relative;
display: block;
margin: auto;
max-width: 100%;
max-height: 100%;
top: 50%;
transform: translateY(-50%);
}
</style>

View File

@@ -0,0 +1,618 @@
<script>
import { onMount } from "svelte";
import Modal from "../util/Modal.svelte";
import PixeldrainLogo from "../util/PixeldrainLogo.svelte";
import DetailsWindow from "./DetailsWindow.svelte";
import FilePreview from "./FilePreview.svelte";
import ListNavigator from "./ListNavigator.svelte";
import Pdf from "./viewers/PDF.svelte";
let is_list = false
let embedded = false
let view_token = ""
let file_preview
let current_file = {
id: "",
name: "loading...",
mime_type: "",
get_href: "",
download_href: "",
icon_href: "",
}
let current_list = {
id: "",
title: "",
files: [],
download_href: "",
}
let list_navigator
let list_shuffle = false
let toggle_shuffle = () => {
list_shuffle = !list_shuffle
list_navigator.set_shuffle(list_shuffle)
}
let sharebar_visible = false
let toolbar_visible = (window.innerWidth > 600)
let toolbar_toggle = () => {
toolbar_visible = !toolbar_visible
if (!toolbar_visible) {
sharebar_visible = false
}
}
let details_window
let details_visible = false
let qr_window
let qr_visible = false
let edit_window
let edit_visible = false
let report_window
let report_visible = false
let embed_window
let embed_visible = false
onMount(() => {
let viewer_data = window.viewer_data
embedded = viewer_data.embedded
if (embedded) {
toolbar_visible = false
}
view_token = viewer_data.view_token
if (viewer_data.type === "list") {
open_list(viewer_data.api_response)
} else {
open_file(viewer_data.api_response)
}
})
const open_list = (list) => {
is_list = true
list.download_href = window.api_endpoint+"/list/"+list.id+"/zip"
list.files.forEach(file => {
file_set_href(file)
})
current_list = list
open_file(list.files[0])
}
const open_file = (file) => {
file_set_href(file)
current_file = file
// Register a file view
fetch(window.api_endpoint + "/file/" + current_file.id + "/view", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: "token=" + view_token
})
}
const file_set_href = file => {
file.get_href = window.api_endpoint+"/file/"+file.id
file.download_href = window.api_endpoint+"/file/"+file.id+"?download"
file.icon_href = window.api_endpoint+"/file/"+file.id+"/thumbnail"
file.timeseries_href = window.api_endpoint+"/file/"+file.id+"/timeseries"
}
let download_frame
const download = () => {
download_frame.src = current_file.download_href
}
const download_list = () => {
if (is_list) {
download_frame.src = current_list.download_href
}
}
let supports_fullscreen = !!document.documentElement.requestFullscreen
let fullscreen = false
const toggle_fullscreen = () => {
if (!fullscreen) {
document.documentElement.requestFullscreen()
document.getElementById("btn_fullscreen_icon").innerText = "fullscreen_exit"
fullscreen = true
} else {
document.exitFullscreen()
document.getElementById("btn_fullscreen_icon").innerText = "fullscreen"
fullscreen = false
}
}
const keyboard_event = evt => {
if (evt.ctrlKey || evt.altKey || evt.metaKey) {
return // prevent custom shortcuts from interfering with system shortcuts
}
if (document.activeElement.type && document.activeElement.type === "text") {
return // Prevent shortcuts from interfering with input fields
}
console.debug("Key pressed: " + evt.keyCode)
switch (evt.keyCode) {
case 65: // A or left arrow key go to previous file
case 37:
if (list_navigator) {
list_navigator.prev()
}
break
case 68: // D or right arrow key go to next file
case 39:
if (list_navigator) {
list_navigator.next()
}
break
case 83:
if (evt.shiftKey) {
download_list() // SHIFT + S downloads all files in list
} else {
download() // S to download the current file
}
break
case 82: // R to toggle list shuffle
if (list_navigator) {
toggle_shuffle()
}
break
case 67: // C to copy to clipboard
this.toolbar.copyUrl()
break
case 73: // I to open the details window
details_window.toggle()
break
case 69: // E to open the edit window
edit_window.toggle()
break
case 77: // M to open the embed window
embed_window.toggle()
break
case 71: // G to grab this file
this.grabFile()
break
case 81: // Q to close the window
window.close()
break
}
}
</script>
<svelte:window on:keydown={keyboard_event}/>
<div id="file_viewer" class="file_viewer">
<div id="headerbar" class="headerbar" class:embedded>
<button on:click={toolbar_toggle} class="button_toggle_toolbar round" class:button_highlight={toolbar_visible}>
<i class="icon">menu</i>
</button>
<a href="/" id="button_home" class="button button_home round" target={embedded ? "_blank" : ""}>
<PixeldrainLogo style="height: 1.6em; width: 1.6em; margin: 0 4px 0 0;"></PixeldrainLogo>
</a>
<div id="file_viewer_headerbar_title" class="file_viewer_headerbar_title">
<div id="list_title">{current_list.title}</div>
<div id="lile_title">{current_file.name}</div>
</div>
{#if embedded && supports_fullscreen}
<button class="round" on:click={toggle_fullscreen}>
<i class="icon" id="btn_fullscreen_icon">fullscreen</i>
</button>
{/if}
</div>
{#if is_list}
<ListNavigator bind:this={list_navigator} files={current_list.files} on:set_file={(e) => { open_file(e.detail) }}></ListNavigator>
{/if}
<div id="file_preview_window" class="file_preview_window">
<div id="toolbar" class="toolbar" class:toolbar_visible><div><div>
<div id="stat_views_label" class="toolbar_label">Views</div>
<div id="stat_views" style="text-align: center;">N/A</div>
<div id="stat_downloads_label" class="toolbar_label">Downloads</div>
<div id="stat_downloads" style="text-align: center;">N/A</div>
<div id="stat_size_label" class="toolbar_label">Size</div>
<div id="stat_size" style="text-align: center;">N/A</div>
<hr/>
<button on:click={download} class="toolbar_button button_full_width">
<i class="icon">save</i>
<span>Download</span>
</button>
{#if is_list}
<button class="toolbar_button button_full_width">
<i class="icon">save</i>
<span>DL all files</span>
</button>
{/if}
<button class="toolbar_button button_full_width">
<i class="icon">content_copy</i>
<span><u>C</u>opy link</span>
</button>
<button on:click={() => sharebar_visible = !sharebar_visible} class="toolbar_button button_full_width" class:button_highlight={sharebar_visible}>
<i class="icon">share</i> Share
</button>
<button class="toolbar_button button_full_width" on:click={qr_window.toggle} class:button_highlight={qr_visible}>
<i class="icon">qr_code</i>
<span>QR code</span>
</button>
{#if is_list}
<button
class="toolbar_button button_full_width"
title="Randomize the order of the files in this list"
class:button_highlight={list_shuffle}
on:click={toggle_shuffle}
>
<i class="icon">shuffle</i>
{#if list_shuffle}
<span>Shuffle&nbsp;&#x2611;</span>
{:else}
<span>Shuffle&nbsp;&#x2610;</span>
{/if}
</button>
{/if}
<button class="toolbar_button button_full_width" on:click={details_window.toggle} class:button_highlight={details_visible}>
<i class="icon">help</i>
<span>Deta<u>i</u>ls</span>
</button>
<hr/>
<button class="toolbar_button button_full_width" on:click={edit_window.toggle} class:button_highlight={edit_visible}>
<i class="icon">edit</i>
<span><u>E</u>dit</span>
</button>
<button class="toolbar_button button_full_width" title="Copy this file to your own pixeldrain account">
<i class="icon">save_alt</i>
<span><u>G</u>rab file</span>
</button>
<button class="toolbar_button button_full_width" title="Include this file in your own webpages" on:click={embed_window.toggle} class:button_highlight={embed_visible}>
<i class="icon">code</i>
<span>E<u>m</u>bed</span>
</button>
<button class="toolbar_button button_full_width" title="Report abuse in this file" on:click={report_window.toggle} class:button_highlight={report_visible}>
<i class="icon">flag</i>
<span>Report</span>
</button>
<br/>
</div></div></div>
<div id="sharebar" class="sharebar" class:sharebar_visible>
Share on:<br/>
<button class="sharebar-button button_full_width" onclick="window.open('mailto:please@set.address?subject=File%20on%20pixeldrain&body=' + window.location.href);">
E-Mail
</button>
<button class="sharebar-button button_full_width" onclick="window.open('https://www.reddit.com/submit?url=' + window.location.href);">
Reddit
</button>
<button class="sharebar-button button_full_width" onclick="window.open('https://twitter.com/share?url=' + window.location.href);">
Twitter
</button>
<button class="sharebar-button button_full_width" onclick="window.open('http://www.facebook.com/sharer.php?u=' + window.location.href);">
Facebook
</button>
<button class="sharebar-button button_full_width" onclick="window.open('http://www.tumblr.com/share/link?url=' + window.location.href);">
Tumblr
</button>
</div>
<div id="file_preview" class="file_preview checkers" class:toolbar_visible>
<FilePreview
file={current_file}
bind:this={file_preview}
on:download={download}
on:prev={() => { if (list_navigator) { list_navigator.prev() }}}
on:next={() => { if (list_navigator) { list_navigator.next() }}}>
</FilePreview>
</div>
<div id="skyscraper" class="skyscraper">
<button id="btn_skyscraper_close" class="round">
<i class="icon">close</i> Close ad
</button>
<div id="skyscraper_ad_space" class="skyscraper_ad_space"></div>
</div>
<!-- This frame will load the download URL when a download button is pressed -->
<iframe bind:this={download_frame} title="File download frame" style="display: none; width: 1px; height: 1px;"></iframe>
</div>
<div id="sponsors" class="sponsors">
<div style="text-align: center; line-height: 1.3em; font-size: 13px;">
Thank you for supporting pixeldrain!
</div>
</div>
<Modal bind:this={details_window} on:is_visible={e => {details_visible = e.detail}} title="File details" width="1200px">
<DetailsWindow file={current_file}></DetailsWindow>
</Modal>
<Modal bind:this={qr_window} on:is_visible={e => {qr_visible = e.detail}} title="QR code">
Hi!
</Modal>
<Modal bind:this={edit_window} on:is_visible={e => {edit_visible = e.detail}} title={"Editing "+current_file.name}>
Hi!
</Modal>
<Modal bind:this={embed_window} on:is_visible={e => {embed_visible = e.detail}} title="Embed file">
Hi!
</Modal>
<Modal bind:this={report_window} on:is_visible={e => {report_visible = e.detail}} title="Report abuse">
Hi!
</Modal>
</div>
<style>
/* Viewer container */
.file_viewer {
position: absolute;
display: flex;
flex-direction: column;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
background-color: var(--layer_2_color);
}
/* Headerbar (row 1) */
.headerbar {
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: row;
text-align: left;
z-index: 10;
box-shadow: none;
padding: 4px;
}
.headerbar.embedded {
padding: 2px;
}
/* Headerbar components */
.headerbar > * {
flex-grow: 0;
flex-shrink: 0;
margin-left: 4px;
margin-right: 4px;
display: inline;
}
.headerbar > .file_viewer_headerbar_title {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
overflow: hidden;
line-height: 1.2em; /* When the page is a list there will be two lines. Dont's want to stretch the container*/
white-space: nowrap;
text-overflow: ellipsis;
justify-content: center;
}
.headerbar > button > .icon {
font-size: 1.6em;
}
.headerbar > .button_home::after {
content: "pixeldrain";
}
@media (max-width: 600px) {
.headerbar > .button_home::after {
content: "pd";
}
}
/* List Navigator (row 2) */
/* File preview area (row 3) */
.file_preview_window {
flex-grow: 1;
flex-shrink: 1;
position: relative;
display: inline-block;
width: auto;
height: auto;
margin: 0;
z-index: 9;
}
.file_preview {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: inline-block;
min-height: 100px;
min-width: 100px;
text-align: center;
vertical-align: middle;
transition: left 0.5s, right 0.5s;
overflow: hidden;
box-shadow: inset 2px 2px 10px 2px var(--shadow_color);
border-radius: 16px;
}
/* Sponsors (row 4) */
.file_viewer > .sponsors {
font-size: 0;
line-height: 0;
}
.file_viewer > .sponsors > .sponsors_banner {
display: block;
margin: auto;
transform-origin: 0 0;
}
/* Toolbars */
.toolbar {
position: absolute;
width: 8em;
z-index: 49;
overflow: hidden;
left: -8em;
bottom: 0;
top: 0;
padding: 0;
text-align: left;
transition: left 0.5s;
background-color: var(--layer_2_color);
}
.toolbar.toolbar_visible { left: 0; }
.file_preview.toolbar_visible { left: 8em; }
.sharebar {
position: absolute;
width: 7em;
left: -8em;
bottom: 0;
top: 0;
overflow-y: scroll;
overflow-x: hidden;
box-shadow: inset 1px 1px 5px var(--shadow_color);
background-color: var(--layer_1_color);
border-radius: 16px;
text-align: center;
z-index: 48;
overflow: hidden;
transition: left 0.5s;
}
.sharebar.sharebar_visible { left: 8em; }
.file_viewer > .file_viewer_window > .skyscraper {
position: absolute;
width: 160px;
z-index: 49;
overflow: hidden;
right: -170px;
bottom: 0;
top: 0;
padding: 0;
text-align: center;
transition: right 0.5s;
background-color: var(--layer_2_color);
}
.file_viewer > .file_viewer_window > .skyscraper > .skyscraper_ad_space {
width: 100%;
height: 100%;
}
/* =====================
== FILE CONTAINERS ==
=====================*/
/* ========================
|| TOOLBAR COMPONENTS ||
======================== */
/* Workaround to hide the scrollbar in non webkit browsers, it's really ugly' */
.toolbar > div {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: -30px;
overflow-y: scroll;
overflow-x: hidden;
}
.toolbar > div > div {
position: absolute;
left: 0;
top: 0;
width: 8em;
height: auto;
text-align: center;
}
.toolbar_button{
text-align: left;
}
.toolbar_button > span {
vertical-align: middle;
}
.toolbar_label {
text-align: left;
padding-left: 10px;
font-size: 0.8em;
line-height: 0.7em;
margin-top: 0.5em;
}
/* =========================
|| SHAREBAR COMPONENTS ||
========================= */
.sharebar-button {text-align: center;}
.sharebar-button > svg,
.sharebar-button > img {
width: 40px;
height: 40px;
}
/* =====================
|| MISC COMPONENTS ||
===================== */
.captcha_popup_captcha > div {
display: inline-block;
}
table {width: auto !important;}
/* Abuse report label*/
.abuse_type_form > label {
display: block;
border-bottom: 1px var(--layer_2_color_border) solid;
padding: 0.5em;
}
.abuse_type_form > label:last-of-type {
border-bottom: none;
}
/* ====================
|| LIST NAVIGATOR ||
==================== */
.file-container{
position: absolute;
top: 100px;
left: 0px;
right: 0px;
bottom: 0px;
width: 100%;
overflow: hidden;
border: none;
}
.file-container-frame{
position: absolute;
width: 100%;
height: 100%;
border: none;
}
.intro_popup {
position: absolute;
width: 380px;
max-width: 80%;
height: auto;
background-color: var(--layer_4_color);
box-shadow: 1px 1px 10px var(--shadow_color);
border-radius: 20px;
z-index: 50;
transition: opacity .4s, left .5s, top .5s;
}
.intro_popup:before {
content: "";
display: block;
position: absolute;
left: 30px;
top: -15px;
border-bottom: 15px solid var(--layer_4_color);
border-left: 15px solid transparent;
border-right: 15px solid transparent;
}
</style>

View File

@@ -0,0 +1,120 @@
<script>
import { createEventDispatcher, onMount } from "svelte";
let dispatch = createEventDispatcher()
export let files = []
let file_list_div
let selected_file_index = 0
onMount(() => {
// Skip to the file defined in the link hash
let matches = location.hash.match(new RegExp('item=([^&]*)'))
let hashID = matches ? matches[1] : null
let idx = 0
if (Number.isInteger(parseInt(hashID))) {
idx = parseInt(hashID)
}
set_item(idx)
})
export const next = () => {
if (shuffle) {
rand_item()
return
}
set_item(selected_file_index+1)
}
export const prev = () => { set_item(selected_file_index-1) }
let history = []
export const rand_item = () => {
// Avoid viewing the same file multiple times
let rand
do {
rand = Math.round(Math.random() * files.length)
console.log("rand is " + rand)
} while(history.indexOf(rand) > -1)
set_item(rand)
}
let shuffle = false
export const set_shuffle = s => { shuffle = s }
export const set_item = idx => {
if (idx >= files.length) {
idx = 0
} else if (idx < 0) {
idx = files.length - 1
}
// Remove the class from the previous selected file
files[selected_file_index].selected = false
selected_file_index = idx
files[idx].selected = true
// Update the URL
location.hash = "item=" + idx
// Add item to history
if(history.length >= (files.length - 6)){
history.shift()
}
history.push(idx)
dispatch("set_file", files[idx])
// Smoothly scroll the navigator to the correct element
let selected_file = file_list_div.children[idx]
let cst = window.getComputedStyle(selected_file)
let itemWidth = selected_file.offsetWidth + parseInt(cst.marginLeft) + parseInt(cst.marginRight)
let start = file_list_div.scrollLeft
let end = ((idx * itemWidth) + (itemWidth / 2)) - (file_list_div.clientWidth / 2)
let steps = 60 // One second
let stepSize = (end - start)/steps
let animateScroll = (pos, step) => {
file_list_div.scrollLeft = pos
if (step < steps) {
requestAnimationFrame(() => {
animateScroll(pos+stepSize, step+1)
})
}
}
animateScroll(start, 0)
}
</script>
<div bind:this={file_list_div} class="list_navigator">
{#each files as file, index}
<div class="list_item file_button" class:file_button_selected={file.selected} on:click={() => { set_item(index) }}>
<img src={file.icon_href+"?width=48&height=48"} alt={file.name} class="list_item_thumbnail" />
{file.name}
</div>
{/each}
</div>
<style>
.list_item {
height: 2.6em !important;
width: 220px !important;
}
.list_navigator {
flex-grow: 0;
flex-shrink: 0;
position: relative;
width: 100%;
border-top: 1px solid var(--layer_2_color_border);
text-align: center;
line-height: 1em;
overflow-x: auto;
overflow-y: hidden;
z-index: 50;
white-space: nowrap;
}
</style>

View File

@@ -0,0 +1,29 @@
<script>
export let file = {
id: "",
name: "",
abuse_type: "",
abuse_reporter_name: "",
}
</script>
<div class="container">
<h1>Unavailable for legal reasons</h1>
<p>
This file has received an abuse report and was taken down.
</p>
<p>
Type of abuse: {file.abuse_type}. Reporter: {file.abuse_reporter_name}.
</p>
</div>
<style>
.container {
position: relative;
display: block;
height: 100%;
width: 100%;
text-align: center;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,106 @@
<script>
import { onMount, createEventDispatcher, tick } from "svelte";
let dispatch = createEventDispatcher()
export let file = {
id: "",
name: "",
mime_type: "",
get_href: "",
}
$: loop = file.name.includes(".loop.")
let player
let playing = false
let audio_reload = false
let media_session = false
$: update_file(file.id)
const update_file = async () => {
if (media_session) {
navigator.mediaSession.metadata = new MediaMetadata({
title: file.name,
artist: "pixeldrain",
album: "unknown",
});
console.log("updating media session")
}
// When the component receives a new ID the video track does not automatically
// start playing the new video. So we use this little hack to make sure that the
// video is unloaded and loaded when the ID changes
audio_reload = true
await tick()
audio_reload = false
}
const toggle_play = () => playing ? player.pause() : player.play()
onMount(() => {
if ('mediaSession' in navigator) {
media_session = true
navigator.mediaSession.setActionHandler('play', () => player.play());
navigator.mediaSession.setActionHandler('pause', () => player.pause());
navigator.mediaSession.setActionHandler('stop', () => player.stop());
navigator.mediaSession.setActionHandler('previoustrack', () => dispatch("prev", {}));
navigator.mediaSession.setActionHandler('nexttrack', () => dispatch("next", {}));
update_file()
}
})
</script>
<div class="container">
<button on:click={() => dispatch("prev") }>
<i class="icon">skip_previous</i>
</button>
<button on:click={() => player.currentTime -= 10 }>
<i class="icon">replay_10</i>
</button>
<button on:click={toggle_play}>
{#if playing}
<i class="icon">pause</i>
{:else}
<i class="icon">play_arrow</i>
{/if}
</button>
<button on:click={() => player.currentTime += 10 }>
<i class="icon">forward_10</i>
</button>
<button on:click={() => dispatch("next") }>
<i class="icon">skip_next</i>
</button>
<br/><br/>
{#if !audio_reload}
<!-- svelte-ignore a11y-media-has-caption -->
<audio
bind:this={player}
class="player"
controls
playsinline
autoplay
loop={loop}
on:pause={() => playing = false }
on:play={() => playing = true }
on:ended={() => {dispatch("next", {})}}
>
<source src={file.get_href} type={file.mime_type} />
</audio>
{/if}
</div>
<style>
.container {
height: 100%;
width: 100%;
margin: 50px 0 0 0;
padding: 0;
overflow-y: auto;
text-align: center;
}
.player {
width: 90%;
}
</style>

View File

@@ -0,0 +1,47 @@
<script>
import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher()
export let file = {
id: "",
name: "",
mime_type: "",
icon_href: "",
}
</script>
<div class="container">
<h1>You are viewing a file on pixeldrain</h1>
<img src={file.icon_href} alt="File icon" class="icon">
<div class="description">
Name: {file.name}<br/>
Type: {file.mime_type}<br/>
No preview is available for this file type. Download to view it locally.
<br/>
<button class="button_highlight" on:click={() => {dispatch("download")}}>
<i class="icon">save</i> Download
</button>
</div>
</div>
<style>
.icon {
display: inline-block;
vertical-align: middle;
}
.description {
display: inline-block;
text-align: left;
padding-left: 8px;
vertical-align: middle;
max-width: 600px;
}
.container {
position: relative;
display: block;
height: 100%;
width: 100%;
text-align: center;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,91 @@
<script>
export let file = {
id: "",
name: "",
mime_type: "",
get_href: "",
}
let container
let zoom = false
let x, y = 0
let dragging = false
const mousedown = (e) => {
if (!dragging && e.which === 1 && zoom) {
x = e.pageX
y = e.pageY
dragging = true
e.preventDefault()
e.stopPropagation()
return false
}
}
const mousemove = (e) => {
if (dragging) {
container.scrollLeft = container.scrollLeft - (e.pageX - x)
container.scrollTop = container.scrollTop - (e.pageY - y)
x = e.pageX
y = e.pageY
e.preventDefault()
e.stopPropagation()
return false
}
}
const mouseup = (e) => {
if (dragging) {
dragging = false
e.preventDefault()
e.stopPropagation()
return false
}
}
</script>
<svelte:window on:mousemove={mousemove} on:mouseup={mouseup} />
<div bind:this={container} class="container" class:zoom>
<img
on:dblclick={() => {zoom = !zoom}}
on:doubletap={() => {zoom = !zoom}}
on:mousedown={mousedown}
class="image" class:zoom
src={file.get_href}
alt={file.name} />
</div>
<style>
.container {
position: relative;
display: block;
height: 100%;
width: 100%;
text-align: center;
overflow: hidden;
}
.container.zoom {
overflow: auto;
}
.image {
position: relative;
display: block;
margin: auto;
max-width: 100%;
max-height: 100%;
top: 50%;
cursor: pointer;
transform: translateY(-50%);
box-shadow: 1px 1px 5px var(--shadow_color);
}
.image.zoom {
max-width: none;
max-height: none;
top: 0;
cursor: move;
transform: none;
}
</style>

View File

@@ -0,0 +1,23 @@
<script>
export let file = {
get_href: "",
}
</script>
<iframe
class="container"
src={"/res/misc/pdf-viewer/web/viewer.html?file="+encodeURIComponent(file.get_href)}
title="PDF viewer">
</iframe>
<style>
.container {
position: relative;
display: block;
height: 100%;
width: 100%;
text-align: center;
overflow: hidden;
border: none;
}
</style>

View File

@@ -0,0 +1,126 @@
<script>
import { onMount } from "svelte";
export let file = {
id: "",
name: "",
mime_type: "",
size: 0,
get_href: "",
}
let container
let text_type = ""
$: update_file(file.id)
const update_file = async () => {
if (file.name.endsWith(".md") || file.name.endsWith(".markdown") || file.mime_type === "text/demo") {
markdown()
} else if (file.name.endsWith(".txt")) {
text()
} else {
code()
}
}
onMount(update_file)
let md_container
const markdown = () => {
text_type = "markdown"
fetch("/u/" + file.id + "/preview").then(resp => {
if (!resp.ok) { return Promise.reject(resp.status) }
return resp.text()
}).then(resp => {
md_container.innerHTML = resp
}).catch(err => {
md_container.innerText = "Error loading file: " + err
})
}
let text_pre
const text = () => {
text_type = "text"
if (file.size > 1 << 20) { // File larger than 1 MiB
text_pre.innerText = "File is too large to view online.\nPlease download and view it locally."
return
}
fetch(file.get_href).then(resp => {
if (!resp.ok) { return Promise.reject(resp.status) }
return resp.text()
}).then(resp => {
text_pre.innerText = resp
}).catch(err => {
text_pre.innerText = "Error loading file: " + err
})
}
let code_pre
let prettyprint = false
const code = () => {
text_type = "code"
if (file.size > 1 << 20) { // File larger than 1 MiB
code_pre.innerText = "File is too large to view online.\nPlease download and view it locally."
return
}
fetch(file.get_href).then(resp => {
if (!resp.ok) { return Promise.reject(resp.status) }
return resp.text()
}).then(resp => {
code_pre.innerText = resp
// Load prettyprint script
if (!prettyprint) {
let prettyprint = document.createElement("script")
prettyprint.src = "https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/run_prettify.js?skin=desert"
container.appendChild(prettyprint)
prettyprint = true
} else {
PR.prettyPrint()
}
}).catch(err => {
code_pre.innerText = "Error loading file: " + err
})
}
</script>
<div bind:this={container} class="container">
{#if text_type === "markdown"}
<div bind:this={md_container}>
Loading...
</div>
{:else if text_type === "text"}
<pre bind:this={text_pre}>
Loading...
</pre>
{:else if text_type === "code"}
<pre bind:this={code_pre} class="pre-container prettyprint linenums">
Loading...
</pre>
{/if}
</div>
<style>
.container {
background: #333 none;
position: relative;
text-align: left;
height: 100%;
width: 100%;
font-size: 0.9em;
line-height: 1.5em;
padding: 5px 5px 5px 20px;
box-sizing: border-box;
overflow-y: scroll;
overflow-x: hidden;
}
.container > pre {
white-space: pre-wrap;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,106 @@
<script>
import { onMount, createEventDispatcher, tick } from "svelte";
let dispatch = createEventDispatcher()
export let file = {
id: "",
name: "",
mime_type: "",
get_href: "",
icon_href: "",
allow_video_player: true,
}
$: loop = file.name.includes(".loop.")
let player
let video_reload = false
let media_session = false
$: update_file(file.id)
const update_file = async () => {
if (media_session) {
navigator.mediaSession.metadata = new MediaMetadata({
title: file.name,
artist: "pixeldrain",
album: "unknown",
});
console.log("updating media session")
}
// When the component receives a new ID the video track does not automatically
// start playing the new video. So we use this little hack to make sure that the
// video is unloaded and loaded when the ID changes
video_reload = true
await tick()
video_reload = false
}
onMount(() => {
if ('mediaSession' in navigator) {
media_session = true
navigator.mediaSession.setActionHandler('play', () => player.play());
navigator.mediaSession.setActionHandler('pause', () => player.pause());
navigator.mediaSession.setActionHandler('stop', () => player.stop());
navigator.mediaSession.setActionHandler('previoustrack', () => dispatch("prev", {}));
navigator.mediaSession.setActionHandler('nexttrack', () => dispatch("next", {}));
update_file()
}
})
let download = () => { dispatch("download", {}) }
</script>
<div class="container">
{#if file.allow_video_player}
{#if !video_reload}
<!-- svelte-ignore a11y-media-has-caption -->
<video
bind:this={player}
controls
playsinline
autoplay
loop={loop}
class="center drop_shadow"
on:ended={() => {dispatch("next", {})}}
>
<source src={file.get_href} type={file.mime_type} />
</video>
{/if}
{:else}
<h1>This is a video file on pixeldrain</h1>
<img src={file.icon_href} alt="Video icon" style="display: inline-block; vertical-align: middle;">
<div style="display: inline-block; text-align: left; padding-left: 8px; vertical-align: middle; max-width: 600px;">
The online video player on pixeldrain has been disabled due to
repeated abuse. You can still watch videos online by upgrading to
Pro. Or download the video and watch it locally on your computer.
<br/>
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5291427&cadence=12" class="button button_highlight">
<i class="icon">upgrade</i> Upgrade to Pro
</a>
<button on:click={download}>
<i class="icon">save</i> Download
</button>
</div>
{/if}
</div>
<style>
.container{
position: relative;
display: block;
height: 100%;
width: 100%;
text-align: center;
overflow: hidden;
}
.center {
position: relative;
display: block;
margin: auto;
max-width: 100%;
max-height: 100%;
top: 50%;
transform: translateY(-50%);
}
</style>

View File

@@ -1,111 +0,0 @@
<script>
import { createEventDispatcher } from 'svelte'
import { formatDataVolume, formatThousands } from '../util/Formatting.svelte'
let dispatch = createEventDispatcher()
let toolbar
export let file
export let sharebar
export let visible = false
export const setVisible = (v) => {
visible = v
if (!visible) {
sharebar.setVisible(false)
}
}
export const toggle = () => { setVisible(!visible) }
</script>
<div bind:this={toolbar} class="toolbar" class:visible><div><div>
<div class="label">Views</div>
<div class="statistic">{formatThousands(file.views)}</div>
<div class="label">Downloads</div>
<div class="statistic">{formatThousands(file.downloads)}</div>
<div class="label">Size</div>
<div class="statistic">{formatDataVolume(file.size)}</div>
<button on:click={()=>{dispatch("download")}} class="toolbar_button button_full_width">
<i class="icon">save</i>
<span>Download</span>
</button>
<button id="btn_download_list" class="toolbar_button button_full_width" style="display: none;">
<i class="icon">save</i>
<span>DL all files</span>
</button>
<button id="btn_copy" class="toolbar_button button_full_width">
<i class="icon">content_copy</i>
<span><u>C</u>opy Link</span>
</button>
<button on:click={sharebar.toggle} class="toolbar_button button_full_width">
<i class="icon">share</i>
<span>Share</span>
</button>
<button id="btn_shuffle" class="toolbar_button button_full_width" style="display: none;">
<i class="icon">shuffle</i>
<span>Shuffle &#x2610;</span>
</button>
<button on:click={()=>{dispatch("details")}} class="toolbar_button button_full_width">
<i class="icon">help</i>
<span>Deta<u>i</u>ls</span>
</button>
<button id="btn_edit" class="toolbar_button button_full_width" style="display: none;">
<i class="icon">edit</i>
<span><u>E</u>dit</span>
</button>
</div></div></div>
<style>
.toolbar {
position: absolute;
width: 8em;
z-index: 49;
overflow: hidden;
float: left;
background-color: var(--layer_1_color);
left: -9em;
bottom: 0;
top: 0;
padding: 0;
text-align: left;
transition: left 0.5s;
}
.visible { left: 0; }
/* Workaround to hide the scrollbar in non webkit browsers, it's really ugly' */
.toolbar > div {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: -30px;
overflow-y: scroll;
overflow-x: hidden;
}
.toolbar > div > div {
position: absolute;
left: 0;
top: 0;
width: 8em;
height: auto;
text-align: center;
}
.toolbar_button{
text-align: left;
}
.toolbar_button > span {
vertical-align: middle;
}
.label {
text-align: left;
padding-left: 10px;
font-size: 0.8em;
line-height: 0.7em;
margin-top: 0.5em;
}
.statistic {
text-align: center;
}
</style>

View File

@@ -271,8 +271,7 @@ onMount(() => {
bind:this={help_modal}
title="File manager help"
width="600px"
on:shown={() => help_modal_visible = true}
on:hidden={() => help_modal_visible = false}
on:is_visible={e => {help_modal_visible = e.detail}}
>
<p>
In the file manager you can see the files you have uploaded and the

View File

@@ -35,7 +35,7 @@ onMount(() => {
datasets: [
{
label: label,
backgroundColor: window.highlight_color,
backgroundColor: "#"+window.style.highlightColor,
borderWidth: 0,
lineTension: 0,
fill: true,

View File

@@ -22,19 +22,18 @@ const load_modal = modal => {
}
const dispatch = createEventDispatcher();
export const show = () => { visible = true; dispatch("shown"); }
export const hide = () => { visible = false; dispatch("hidden"); }
export const toggle = () => {
if (visible) {
hide()
} else {
show()
}
export const show = () => { set_visible(true) }
export const hide = () => { set_visible(false) }
export const toggle = () => { set_visible(!visible) }
export const set_visible = vis => {
visible = vis
dispatch("is_visible", visible)
}
const keydown = e => {
if (e.key === 'Escape') {
hide();
set_visible(false);
return;
}
};

View File

@@ -7,7 +7,6 @@
<style>
svg {
color: var(--highlight_color);
fill: currentColor;
}
</style>

View File

@@ -56,7 +56,7 @@ func (vd *viewerData) adType(files []pixelapi.ListFile) {
none = 0
aAds = 1
patreon = 2
soulStudio = 3
adshares = 3
amarulaSolutions = 4
adMaven = 5
adSterra = 6
@@ -76,6 +76,8 @@ func (vd *viewerData) adType(files []pixelapi.ListFile) {
// Skyscrapers
aAdsSkyscraper = "a-ads"
pixfutureSkyscraper = "pixfuture"
adsPlusSkyscraper = "adsplus"
adsharesSkyscraper = "adshares"
// Floaters
// propellerFloat = 1
@@ -108,10 +110,10 @@ func (vd *viewerData) adType(files []pixelapi.ListFile) {
panic(fmt.Errorf("random number generator returned unrecognised number: %d", i))
}
switch i := rand.Intn(4); i {
switch i := rand.Intn(2); i {
case 0:
vd.AdSkyscraperType = aAdsSkyscraper
case 1, 2, 3:
case 1:
vd.AdSkyscraperType = pixfutureSkyscraper
default:
panic(fmt.Errorf("random number generator returned unrecognised number: %d", i))

View File

@@ -0,0 +1,156 @@
package webcontroller
import (
"fmt"
"net/http"
"strings"
"time"
"fornaxian.tech/pixeldrain_api_client/pixelapi"
"github.com/Fornaxian/log"
"github.com/julienschmidt/httprouter"
)
type fileViewerData struct {
Type string `json:"type"` // file or list
APIResponse interface{} `json:"api_response"`
CaptchaKey string `json:"captcha_key"`
ViewToken string `json:"view_token"`
AdBannerType string `json:"ad_banner_type"`
AdSkyscraperType string `json:"ad_skyscraper_type"`
AdFloaterType string `json:"ad_floater_type"`
Embedded bool `json:"embedded"`
FileAdsEnabled bool `json:"file_ads_enabled"`
UserAdsEnabled bool `json:"user_ads_enabled"`
}
// ServeFileViewer controller for GET /u/:id
func (wc *WebController) serveSvelteFile(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
// If the user agent is Wget we redirect it to the API so that the file can
// be downloaded directly
if strings.HasPrefix(r.UserAgent(), "Wget/") {
http.Redirect(w, r, "/api/file/"+p.ByName("id"), http.StatusSeeOther)
return
}
var err error
var ids = strings.Split(p.ByName("id"), ",")
var templateData = wc.newTemplateData(w, r)
var files []pixelapi.ListFile
for _, id := range ids {
inf, err := templateData.PixelAPI.GetFileInfo(id)
if err != nil {
if pixelapi.ErrIsServerError(err) {
wc.templates.Get().ExecuteTemplate(w, "500", templateData)
return
}
continue
}
files = append(files, pixelapi.ListFile{FileInfo: inf})
}
if len(files) == 0 {
w.WriteHeader(http.StatusNotFound)
wc.templates.Get().ExecuteTemplate(w, "file_not_found", templateData)
return
}
templateData.OGData = wc.metadataFromFile(files[0].FileInfo)
var vd = fileViewerData{
CaptchaKey: wc.captchaKey(),
ViewToken: wc.viewTokenOrBust(),
FileAdsEnabled: files[0].ShowAds,
UserAdsEnabled: !(templateData.Authenticated && templateData.User.Subscription.DisableAdDisplay),
}
if len(ids) > 1 {
templateData.Title = fmt.Sprintf("%d files on pixeldrain", len(files))
vd.Type = "list"
vd.APIResponse = pixelapi.ListInfo{
Success: true,
Title: "Multiple files",
DateCreated: time.Now(),
Files: files,
}
} else {
templateData.Title = fmt.Sprintf("%s ~ pixeldrain", files[0].Name)
vd.Type = "file"
vd.APIResponse = files[0].FileInfo
}
if _, ok := r.URL.Query()["embed"]; ok {
vd.Embedded = true
}
templateData.Other = vd
for _, file := range files {
if file.AbuseType != "" {
w.WriteHeader(http.StatusUnavailableForLegalReasons)
break
}
}
err = wc.templates.Get().ExecuteTemplate(w, "file_viewer_svelte", templateData)
if err != nil && !strings.Contains(err.Error(), "broken pipe") {
log.Error("Error executing template file_viewer: %s", err)
}
}
func (wc *WebController) serveSvelteList(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
// If the user agent is Wget we redirect it to the API so that the file can
// be downloaded directly
if strings.HasPrefix(r.UserAgent(), "Wget/") {
http.Redirect(w, r, "/api/list/"+p.ByName("id")+"/zip", http.StatusSeeOther)
return
}
var templateData = wc.newTemplateData(w, r)
var list, err = templateData.PixelAPI.GetListID(p.ByName("id"))
if err != nil {
if err, ok := err.(pixelapi.Error); ok && err.Status == http.StatusNotFound {
w.WriteHeader(http.StatusNotFound)
wc.templates.Get().ExecuteTemplate(w, "list_not_found", templateData)
} else {
log.Error("API request error occurred: %s", err)
w.WriteHeader(http.StatusInternalServerError)
wc.templates.Get().ExecuteTemplate(w, "500", templateData)
}
return
}
if len(list.Files) == 0 {
w.WriteHeader(http.StatusNotFound)
wc.templates.Get().ExecuteTemplate(w, "list_not_found", templateData)
return
}
templateData.Title = fmt.Sprintf("%s ~ pixeldrain", list.Title)
templateData.OGData = wc.metadataFromList(list)
var vd = fileViewerData{
Type: "list",
CaptchaKey: wc.captchaSiteKey,
ViewToken: wc.viewTokenOrBust(),
FileAdsEnabled: list.Files[0].ShowAds,
UserAdsEnabled: !(templateData.Authenticated && templateData.User.Subscription.DisableAdDisplay),
APIResponse: list,
}
if _, ok := r.URL.Query()["embed"]; ok {
vd.Embedded = true
}
templateData.Other = vd
for _, file := range list.Files {
if file.AbuseType != "" {
w.WriteHeader(http.StatusUnavailableForLegalReasons)
break
}
}
err = wc.templates.Get().ExecuteTemplate(w, "file_viewer_svelte", templateData)
if err != nil && !strings.Contains(err.Error(), "broken pipe") {
log.Error("Error executing template file_viewer: %s", err)
}
}

View File

@@ -135,6 +135,8 @@ func New(
{GET, "history" /* */, wc.serveTemplate("history_cookies", handlerOpts{})},
{GET, "u/:id" /* */, wc.serveFileViewer},
{GET, "u/:id/preview" /* */, wc.serveFilePreview},
{GET, "u2/:id" /* */, wc.serveSvelteFile},
{GET, "l2/:id" /* */, wc.serveSvelteList},
{GET, "l/:id" /* */, wc.serveListViewer},
{GET, "d/*path" /* */, wc.serveDirectory},
{GET, "t" /* */, wc.serveTemplate("text_editor", handlerOpts{})},