Finish most of the new file viewer
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
<script>
|
||||
window.api_endpoint = '{{.APIEndpoint}}';
|
||||
window.viewer_data = {{.Other}};
|
||||
window.user_authenticated = {{.Authenticated}};
|
||||
</script>
|
||||
|
||||
<link rel='stylesheet' href='/res/svelte/file_viewer.css'>
|
||||
|
@@ -88,8 +88,8 @@
|
||||
window.api_endpoint = '{{.APIEndpoint}}';
|
||||
window.user = {{.User}};
|
||||
</script>
|
||||
<link rel='stylesheet' href='/res/svelte/home_page.css?v2'>
|
||||
<script defer src='/res/svelte/home_page.js?v2'></script>
|
||||
<link rel='stylesheet' href='/res/svelte/home_page.css?v3'>
|
||||
<script defer src='/res/svelte/home_page.js?v3'></script>
|
||||
</head>
|
||||
<body>
|
||||
{{template "page_top" .}}
|
||||
|
150
svelte/src/file_viewer/AdLeaderboard.svelte
Normal file
150
svelte/src/file_viewer/AdLeaderboard.svelte
Normal file
@@ -0,0 +1,150 @@
|
||||
<script>
|
||||
import { onMount } from "svelte"
|
||||
|
||||
let container
|
||||
let banner
|
||||
let ad_type = ""
|
||||
|
||||
onMount(() => {
|
||||
switch (Math.floor(Math.random() * 20)) {
|
||||
case 0:
|
||||
ad_type = "publisherrest_1"
|
||||
break
|
||||
case 1:
|
||||
ad_type = "publisherrest_2"
|
||||
break
|
||||
case 2:
|
||||
ad_type = "publisherrest_3"
|
||||
break
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
ad_type = "brave"
|
||||
break
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
case 9:
|
||||
case 10:
|
||||
case 11:
|
||||
case 12:
|
||||
ad_type = "ads.plus"
|
||||
break
|
||||
case 13:
|
||||
case 14:
|
||||
case 15:
|
||||
case 16:
|
||||
case 17:
|
||||
case 18:
|
||||
case 19:
|
||||
ad_type = "pixfuture"
|
||||
break
|
||||
}
|
||||
|
||||
resize()
|
||||
})
|
||||
|
||||
// We scale the size of the banner based on the size of the screen. But because
|
||||
// some things don't scale easily like iframes and javascript ads we use a CSS
|
||||
// transformation instead of changing the actual dimensions
|
||||
const resize = () => {
|
||||
if (!banner) {
|
||||
return
|
||||
}
|
||||
|
||||
let scaleWidth = 1
|
||||
let scaleHeight = 1
|
||||
let minWindowHeight = 800
|
||||
let bannerWidth = banner.offsetWidth
|
||||
let bannerHeight = banner.offsetHeight
|
||||
|
||||
if (window.innerWidth < bannerWidth) {
|
||||
scaleWidth = window.innerWidth / bannerWidth
|
||||
}
|
||||
if (window.innerHeight < minWindowHeight) {
|
||||
scaleHeight = window.innerHeight / minWindowHeight
|
||||
}
|
||||
|
||||
// The smaller scale is the scale we'll use
|
||||
let scale = scaleWidth < scaleHeight ? scaleWidth : scaleHeight
|
||||
|
||||
// Because of the scale transformation the automatic margins don't work
|
||||
// anymore. So we have to manually calculate the margin. Here 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 calculated
|
||||
// to account for the smaller size.
|
||||
let offset = (window.innerWidth - (bannerWidth * scale)) / 2
|
||||
|
||||
container.style.height = (bannerHeight * scale) + "px"
|
||||
banner.style.marginLeft = offset + "px"
|
||||
banner.style.transform = "scale(" + scale + ")"
|
||||
}
|
||||
|
||||
const ads_plus = () => {
|
||||
window.googletag = window.googletag || {cmd: []};
|
||||
googletag.cmd.push(function() {
|
||||
googletag.defineSlot('/21673142571/299__pixeldrain.com__default__728x90_1', [728, 90], 'div-gpt-ad-pixeldraincom728x90_1').addService(googletag.pubads());
|
||||
googletag.pubads().collapseEmptyDivs();
|
||||
googletag.enableServices();
|
||||
});
|
||||
googletag.cmd.push(function() { googletag.display('div-gpt-ad-pixeldraincom728x90_1'); });
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={resize} on:load={resize}/>
|
||||
|
||||
<svelte:head>
|
||||
{#if ad_type === "ads.plus"}
|
||||
<script on:load={ads_plus} async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
|
||||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
<div bind:this={container}>
|
||||
{#if ad_type === "publisherrest_1"}
|
||||
<div style="text-align: center; line-height: 1.4em; font-size: 22px;">
|
||||
<a href="https://pixeldrain.com/vouchercodes/" class="button button_highlight" style="margin: 8px;">
|
||||
<i class="icon">shopping_cart</i>
|
||||
Click here for online shopping discounts!
|
||||
<i class="icon">shopping_cart</i>
|
||||
</a>
|
||||
</div>
|
||||
{:else if ad_type === "publisherrest_2"}
|
||||
<div style="text-align: center; line-height: 1.4em; font-size: 22px;">
|
||||
<a href="https://pixeldrain.com/vouchercodes/" class="button button_highlight" style="margin: 8px;">
|
||||
<i class="icon">shopping_cart</i>
|
||||
Check our online shopping discounts!
|
||||
<i class="icon">shopping_cart</i>
|
||||
</a>
|
||||
</div>
|
||||
{:else if ad_type === "publisherrest_3"}
|
||||
<div style="text-align: center; line-height: 1.4em; font-size: 22px;">
|
||||
<a href="https://pixeldrain.com/vouchercodes/" class="button button_highlight" style="margin: 8px;">
|
||||
<i class="icon">shopping_cart</i>
|
||||
Free coupon codes for online shopping!
|
||||
<i class="icon">shopping_cart</i>
|
||||
</a>
|
||||
</div>
|
||||
{:else if ad_type === "brave"}
|
||||
<a bind:this={banner} class="banner" style="display: inline-block; width: 728px; height: 90px;" href="/click/MdUXxSov?target=https%3A%2F%2Fbrave.com%2Fpix009">
|
||||
<img src="/res/img/misc/brave-728x90.png" style="width: 100%; height: 100%" alt="Brave ad"/>
|
||||
</a>
|
||||
{:else if ad_type === "ads.plus"}
|
||||
<!-- This is the tag for the unit and should be placed in the respective ad spot in the body part of the page -->
|
||||
<!-- /21673142571/299__pixeldrain.com__default__728x90_1 -->
|
||||
<div bind:this={banner} class="banner" id='div-gpt-ad-pixeldraincom728x90_1' style='width: 728px; height: 90px;'>
|
||||
</div>
|
||||
{:else if ad_type === "pixfuture"}
|
||||
<!-- AuctionX Display platform tag START -->
|
||||
<div bind:this={banner} class="banner" id="27517x728x90x4605x_ADSLOT1" clickTrack="%%CLICK_URL_ESC%%" style="display: block; margin: auto;"></div>
|
||||
<script type="text/javascript" async src="https://served-by.pixfuture.com/www/delivery/headerbid.js" slotId="27517x728x90x4605x_ADSLOT1" refreshTime="5" refreshInterval="60"></script>
|
||||
<!-- AuctionX Display platform tag END -->
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.banner {
|
||||
display: block;
|
||||
margin: auto;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
</style>
|
117
svelte/src/file_viewer/AdSkyscraper.svelte
Normal file
117
svelte/src/file_viewer/AdSkyscraper.svelte
Normal file
@@ -0,0 +1,117 @@
|
||||
<script>
|
||||
import { createEventDispatcher, onMount, tick } from "svelte"
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
let container
|
||||
let ad_type = ""
|
||||
let visible = false
|
||||
|
||||
onMount(async () => {
|
||||
if (document.body.clientWidth < 800) {
|
||||
visible = false
|
||||
dispatch("visibility", false)
|
||||
return
|
||||
}
|
||||
|
||||
// If the ad popup was dismissed less than 24 hours ago we don't show it
|
||||
let dismissal = +localStorage.getItem("viewer_skyscraper_ad_dismissed")
|
||||
let now = new Date().getTime()
|
||||
|
||||
if (dismissal > 0 && now - dismissal < 1000 * 60 * 60 * 24) {
|
||||
console.log("Skyscraper dismissed")
|
||||
visible = false
|
||||
dispatch("visibility", false)
|
||||
return
|
||||
}
|
||||
|
||||
switch (Math.floor(Math.random() * 3)) {
|
||||
case 0:
|
||||
ad_type = "a-ads"
|
||||
break
|
||||
case 1:
|
||||
ad_type = "pixfuture"
|
||||
break
|
||||
case 2:
|
||||
ad_type = "ads.plus"
|
||||
break
|
||||
}
|
||||
|
||||
visible = true
|
||||
await tick()
|
||||
dispatch("visibility", true)
|
||||
container.style.right = "0"
|
||||
})
|
||||
|
||||
const close = () => {
|
||||
container.style.right = -container.offsetWidth + "px"
|
||||
dispatch("visibility", false)
|
||||
|
||||
localStorage.setItem("viewer_skyscraper_ad_dismissed", new Date().getTime())
|
||||
|
||||
// Remove the ad from the DOM to save memory
|
||||
setTimeout(() => { visible = false }, 1000)
|
||||
}
|
||||
|
||||
const ads_plus = () => {
|
||||
window.googletag = window.googletag || {cmd: []};
|
||||
googletag.cmd.push(function() {
|
||||
googletag.defineSlot('/21673142571/299__pixeldrain.com__default__160x600_1', [160, 600], 'div-gpt-ad-pixeldraincom160x600_1').addService(googletag.pubads());
|
||||
googletag.pubads().collapseEmptyDivs();
|
||||
googletag.enableServices();
|
||||
});
|
||||
googletag.cmd.push(function() { googletag.display('div-gpt-ad-pixeldraincom160x600_1'); });
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
{#if ad_type === "ads.plus"}
|
||||
<script on:load={ads_plus} async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
|
||||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
{#if visible}
|
||||
<div class="skyscraper" bind:this={container}>
|
||||
<button on:click={close} class="round">
|
||||
<i class="icon">close</i> Close ad
|
||||
</button>
|
||||
<div class="ad_space">
|
||||
{#if ad_type === "a-ads"}
|
||||
<iframe
|
||||
data-aa="1811738"
|
||||
src="//ad.a-ads.com/1811738?size=160x600&background_color={window.style.layer2Color}&text_color={window.style.textColor}&title_color={window.style.highlightColor}&title_hover_color={window.style.highlightColor}&link_color={window.style.highlightColor}&link_hover_color={window.style.highlightColor}"
|
||||
style="width:160px; height:600px; border:0px; padding:0; overflow:hidden; background-color: transparent;"
|
||||
title="A-ads advertisement">
|
||||
</iframe>
|
||||
{:else if ad_type === "ads.plus"}
|
||||
<!-- /21673142571/299__pixeldrain.com__default__160x600_1 -->
|
||||
<div id='div-gpt-ad-pixeldraincom160x600_1' style='width: 160px; height: 600px;'></div>
|
||||
{:else if ad_type === "pixfuture"}
|
||||
<!-- AuctionX Display platform tag START -->
|
||||
<div id="27513x160x600x4605x_ADSLOT1" clickTrack="%%CLICK_URL_ESC%%" style="display: block; margin: auto;"></div>
|
||||
<script type="text/javascript" async src="https://served-by.pixfuture.com/www/delivery/headerbid.js" slotId="27513x160x600x4605x_ADSLOT1" refreshTime="5" refreshInterval="60"></script>
|
||||
<!-- AuctionX Display platform tag END -->
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.skyscraper {
|
||||
position: absolute;
|
||||
width: 160px;
|
||||
z-index: 49;
|
||||
overflow: hidden;
|
||||
right: -160px;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
transition: right 0.5s;
|
||||
background-color: var(--layer_2_color);
|
||||
}
|
||||
.ad_space {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
89
svelte/src/file_viewer/EditWindow.svelte
Normal file
89
svelte/src/file_viewer/EditWindow.svelte
Normal file
@@ -0,0 +1,89 @@
|
||||
<script>
|
||||
export let file = {
|
||||
id: "",
|
||||
name: "",
|
||||
get_href: "",
|
||||
}
|
||||
|
||||
let file_name = ""
|
||||
let result_success = false
|
||||
let result_text = ""
|
||||
|
||||
$: update_file(file.id)
|
||||
let update_file = () => {
|
||||
file_name = file.name
|
||||
}
|
||||
|
||||
let rename_file = async e => {
|
||||
e.preventDefault()
|
||||
|
||||
const form = new FormData()
|
||||
form.append("action", "rename")
|
||||
form.append("name", file_name)
|
||||
|
||||
try {
|
||||
const resp = await fetch(file.get_href, { method: "POST", body: form });
|
||||
if (resp.status >= 400) {
|
||||
throw (await resp.json()).message
|
||||
}
|
||||
|
||||
result_success = true
|
||||
result_text = "File name has been changed. Reload the page to see the changes"
|
||||
} catch (err) {
|
||||
result_success = false
|
||||
result_text = "Could not change file name: " + err
|
||||
}
|
||||
}
|
||||
|
||||
let delete_file = async e => {
|
||||
if (!confirm("Are you sure you want to delete '" + file.name + "'?")) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch(file.get_href, { method: "DELETE" });
|
||||
if (resp.status >= 400) {
|
||||
throw (await resp.json()).message
|
||||
}
|
||||
|
||||
result_success = true
|
||||
result_text = "This file has been deleted, you can close the page"
|
||||
} catch (err) {
|
||||
result_success = false
|
||||
result_text = "Could not delete file: " + err
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if result_text !== ""}
|
||||
<div class:highlight_green={result_success} class:highligt_red={!result_success}>
|
||||
{result_text}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<h3>Rename</h3>
|
||||
<form on:submit={rename_file} style="display: flex; width: 100%">
|
||||
<input bind:value={file_name} type="text" style="flex: 1 1 auto"/>
|
||||
<button type="submit" style="flex: 0 0 auto">
|
||||
<i class="icon">save</i> Save
|
||||
</button>
|
||||
</form>
|
||||
<h3>Delete</h3>
|
||||
<p>
|
||||
When you delete a file it cannot be recovered.
|
||||
Nobody will be able to download it and the link will
|
||||
stop working. The file will also disappear from any
|
||||
lists it's contained in.
|
||||
</p>
|
||||
<div style="text-align: center;">
|
||||
<button on:click={delete_file} class="button_red">
|
||||
<i class="icon small">delete</i> Delete this file
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
75
svelte/src/file_viewer/EmbedWindow.svelte
Normal file
75
svelte/src/file_viewer/EmbedWindow.svelte
Normal file
@@ -0,0 +1,75 @@
|
||||
<script>
|
||||
import { copy_text, domain_url } from "../util/Util.svelte";
|
||||
|
||||
|
||||
export let file = {
|
||||
id: "",
|
||||
}
|
||||
export let list = {
|
||||
id: "",
|
||||
}
|
||||
|
||||
let embed_html = ""
|
||||
let preview_area
|
||||
|
||||
$: update_file(file.id, list.id)
|
||||
let update_file = () => {
|
||||
let url
|
||||
if (list.id === "") {
|
||||
// Not a list, use file ID
|
||||
url = domain_url()+"/u/"+file.id+"?embed"
|
||||
} else {
|
||||
url = domain_url()+"/l/"+list.id+"?embed"
|
||||
}
|
||||
|
||||
embed_html = `<iframe ` +
|
||||
`src="${url}" ` +
|
||||
`style="border: none; width: 800px; max-width: 100%; height: 500px; border-radius: 16px;"` +
|
||||
`></iframe>`
|
||||
}
|
||||
|
||||
let copy_status = "" // empty, success or error
|
||||
const copy = () => {
|
||||
if (copy_text(embed_html)) {
|
||||
copy_status = "success"
|
||||
} else {
|
||||
copy_status = "error"
|
||||
alert("Your browser does not support copying text.")
|
||||
}
|
||||
}
|
||||
|
||||
const example = () => {
|
||||
preview_area.innerHTML = embed_html
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
You can embed pixeldrain's file viewer in your own web pages. We
|
||||
have created a special HTML code which renders a minimalistic
|
||||
version of the file viewer where the title bar is a bit thinner and
|
||||
the toolbar is collapsed by default.
|
||||
</p>
|
||||
<p>
|
||||
Unless it was uploaded using a pixeldrain Pro account the embedded
|
||||
file will also show advertisements.
|
||||
</p>
|
||||
<h3>Code</h3>
|
||||
<textarea bind:value={embed_html} style="width: 100%; height: 4em; margin: 0;"></textarea>
|
||||
<br/>
|
||||
<button on:click={copy} class:button_highlight={copy_status === "success"} class:button_red={copy_status === "error"}>
|
||||
<i class="icon">content_copy</i>
|
||||
{#if copy_status === "success"}
|
||||
Copied!
|
||||
{:else if copy_status === "error"}
|
||||
Error!
|
||||
{:else}
|
||||
Copy HTML
|
||||
{/if}
|
||||
</button>
|
||||
<button on:click={example}>
|
||||
<i class="icon">visibility</i> Show example
|
||||
</button>
|
||||
<h3>Example</h3>
|
||||
<div bind:this={preview_area} style="text-align: center;"></div>
|
||||
</div>
|
99
svelte/src/file_viewer/FileStats.svelte
Normal file
99
svelte/src/file_viewer/FileStats.svelte
Normal file
@@ -0,0 +1,99 @@
|
||||
<script>
|
||||
import { formatDataVolume, formatThousands } from "../util/Formatting.svelte"
|
||||
|
||||
export let file = {
|
||||
id: "",
|
||||
views: 0,
|
||||
size: 0,
|
||||
downloads: 0,
|
||||
bandwidth_used: 0,
|
||||
}
|
||||
|
||||
let views = 0
|
||||
let downloads = 0
|
||||
let size = 0
|
||||
let socket = null
|
||||
let error_msg = ""
|
||||
|
||||
$: update_stats(file.id)
|
||||
let update_stats = (id) => {
|
||||
if (id === "") {
|
||||
return
|
||||
}
|
||||
|
||||
views = file.views
|
||||
if (file.size === 0) {
|
||||
downloads = file.downloads
|
||||
} else {
|
||||
downloads = Math.round(file.bandwidth_used / file.size)
|
||||
}
|
||||
size = file.size
|
||||
|
||||
// If the socket is already active we need to close it
|
||||
if (socket !== null) {
|
||||
// Disable the error handler so it doesn't start retrying the connection
|
||||
socket.onerror = null
|
||||
socket.close()
|
||||
socket = null
|
||||
}
|
||||
|
||||
console.log("opening socket for", id)
|
||||
socket = new WebSocket(
|
||||
location.origin.replace(/^http/, 'ws') + "/api/file/" + id + "/stats"
|
||||
)
|
||||
socket.onmessage = msg => {
|
||||
let j = JSON.parse(msg.data)
|
||||
console.debug("WS update", j)
|
||||
|
||||
error_msg = ""
|
||||
views = j.views
|
||||
if (file.size === 0) {
|
||||
downloads = j.downloads
|
||||
} else {
|
||||
downloads = Math.round(j.bandwidth / file.size)
|
||||
}
|
||||
}
|
||||
socket.onerror = err => {
|
||||
if (socket === null) {
|
||||
return
|
||||
}
|
||||
console.error("WS error", err)
|
||||
socket.close()
|
||||
socket = null
|
||||
error_msg = "failed to get stats, retrying..."
|
||||
|
||||
window.setTimeout(() => {
|
||||
if (socket === null) {
|
||||
update_stats(file.id)
|
||||
}
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if error_msg !== ""}
|
||||
{error_msg}
|
||||
{:else}
|
||||
<div class="label">Views</div>
|
||||
<div class="stat">{formatThousands(views)}</div>
|
||||
<div class="label">Downloads</div>
|
||||
<div class="stat">{formatThousands(downloads)}</div>
|
||||
<div class="label">Size</div>
|
||||
<div class="stat">{formatDataVolume(size, 3)}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.label {
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
font-size: 0.8em;
|
||||
line-height: 0.7em;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
.stat {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
@@ -1,12 +1,18 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { onMount, tick } 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";
|
||||
import FileStats from "./FileStats.svelte";
|
||||
import { copy_text, domain_url } from "../util/Util.svelte";
|
||||
import EditWindow from "./EditWindow.svelte";
|
||||
import EmbedWindow from "./EmbedWindow.svelte";
|
||||
import ReportWindow from "./ReportWindow.svelte";
|
||||
import IntroPopup from "./IntroPopup.svelte";
|
||||
import AdLeaderboard from "./AdLeaderboard.svelte";
|
||||
import AdSkyscraper from "./AdSkyscraper.svelte";
|
||||
|
||||
let is_list = false
|
||||
let embedded = false
|
||||
@@ -15,7 +21,14 @@ let file_preview
|
||||
let current_file = {
|
||||
id: "",
|
||||
name: "loading...",
|
||||
size: 0,
|
||||
bandwidth_used: 0,
|
||||
downloads: 0,
|
||||
views: 0,
|
||||
mime_type: "",
|
||||
availability: "",
|
||||
show_ads: false,
|
||||
can_edit: false,
|
||||
get_href: "",
|
||||
download_href: "",
|
||||
icon_href: "",
|
||||
@@ -26,6 +39,7 @@ let current_list = {
|
||||
files: [],
|
||||
download_href: "",
|
||||
}
|
||||
let button_home
|
||||
let list_navigator
|
||||
let list_shuffle = false
|
||||
let toggle_shuffle = () => {
|
||||
@@ -51,6 +65,7 @@ let report_window
|
||||
let report_visible = false
|
||||
let embed_window
|
||||
let embed_visible = false
|
||||
let skyscraper_visible = false
|
||||
|
||||
onMount(() => {
|
||||
let viewer_data = window.viewer_data
|
||||
@@ -70,15 +85,16 @@ onMount(() => {
|
||||
})
|
||||
|
||||
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])
|
||||
|
||||
// Setting is_list to true activates the ListNavgator, which makes sure the
|
||||
// correct file is opened
|
||||
is_list = true
|
||||
}
|
||||
|
||||
const open_file = (file) => {
|
||||
@@ -101,8 +117,64 @@ const file_set_href = file => {
|
||||
}
|
||||
|
||||
let download_frame
|
||||
let is_captcha_script_loaded = false
|
||||
let download_captcha_window
|
||||
let captcha_type = "" // rate_limit or malware
|
||||
let captcha_window_title = ""
|
||||
let captcha_container
|
||||
const download = () => {
|
||||
download_frame.src = current_file.download_href
|
||||
if (!window.viewer_data.captcha_key) {
|
||||
console.debug("Server doesn't support captcha, starting download")
|
||||
download_frame.src = current_file.download_href
|
||||
return
|
||||
}
|
||||
if (current_file.availability === "") {
|
||||
console.debug("File is available, starting download")
|
||||
download_frame.src = current_file.download_href
|
||||
return
|
||||
}
|
||||
|
||||
console.debug("File is not readily available, showing captcha dialog")
|
||||
|
||||
// When the captcha is filled in by the user this function is called. Here
|
||||
// we trigger the download using the captcha token Google provided us with
|
||||
let captcha_complete_callback = token => {
|
||||
// Download the file using the recaptcha token
|
||||
download_frame.src = current_file.download_href + "&recaptcha_response=" + token
|
||||
download_captcha_window.hide()
|
||||
}
|
||||
|
||||
// Function which will be called when the captcha script is loaded. This
|
||||
// renders the checkbox in the modal window
|
||||
window.captcha_script_loaded = async () => {
|
||||
download_captcha_window.show()
|
||||
await tick()
|
||||
grecaptcha.render(captcha_container, {
|
||||
sitekey: window.viewer_data.captcha_key,
|
||||
theme: "dark",
|
||||
callback: captcha_complete_callback,
|
||||
})
|
||||
}
|
||||
|
||||
if (current_file.availability === "file_rate_limited_captcha_required") {
|
||||
captcha_type = "rate_limit"
|
||||
captcha_window_title = "Rate limiting enabled!"
|
||||
} else if (current_file.availability === "virus_detected_captcha_required") {
|
||||
captcha_type = "malware"
|
||||
captcha_window_title = "Malware warning!"
|
||||
}
|
||||
|
||||
if (is_captcha_script_loaded) {
|
||||
console.debug("Captcha script is already loaded. Show the modal")
|
||||
captcha_script_loaded()
|
||||
} else {
|
||||
console.debug("Captcha script has not been loaded yet. Embedding now")
|
||||
|
||||
let script = document.createElement("script")
|
||||
script.src = "https://www.google.com/recaptcha/api.js?onload=captcha_script_loaded&render=explicit"
|
||||
document.body.appendChild(script)
|
||||
is_captcha_script_loaded = true
|
||||
}
|
||||
}
|
||||
const download_list = () => {
|
||||
if (is_list) {
|
||||
@@ -124,6 +196,42 @@ const toggle_fullscreen = () => {
|
||||
}
|
||||
}
|
||||
|
||||
let copy_url_status = "" // empty, copied, or error
|
||||
const copy_url = () => {
|
||||
if (copy_text(window.location.href)) {
|
||||
copy_url_status = "copied"
|
||||
} else {
|
||||
copy_url_status = "error"
|
||||
alert("Your browser does not support copying text.")
|
||||
}
|
||||
|
||||
setTimeout(() => { copy_url_status = "" }, 60000)
|
||||
}
|
||||
|
||||
const grab_file = async () => {
|
||||
if (!window.user_authenticated || current_file.can_edit) {
|
||||
return
|
||||
}
|
||||
|
||||
const form = new FormData()
|
||||
form.append("grab_file", current_file.id)
|
||||
|
||||
try {
|
||||
const resp = await fetch(
|
||||
window.api_endpoint + "/file",
|
||||
{ method: "POST", body: form },
|
||||
);
|
||||
if (resp.status >= 400) {
|
||||
throw (await resp.json()).message
|
||||
}
|
||||
|
||||
window.open("/u/" + (await resp.json()).id, "_blank")
|
||||
} catch (err) {
|
||||
alert("Failed to grab file: " + err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const keyboard_event = evt => {
|
||||
if (evt.ctrlKey || evt.altKey || evt.metaKey) {
|
||||
return // prevent custom shortcuts from interfering with system shortcuts
|
||||
@@ -159,13 +267,15 @@ const keyboard_event = evt => {
|
||||
}
|
||||
break
|
||||
case 67: // C to copy to clipboard
|
||||
this.toolbar.copyUrl()
|
||||
copy_url()
|
||||
break
|
||||
case 73: // I to open the details window
|
||||
details_window.toggle()
|
||||
break
|
||||
case 69: // E to open the edit window
|
||||
edit_window.toggle()
|
||||
if (current_file.can_edit) {
|
||||
edit_window.toggle()
|
||||
}
|
||||
break
|
||||
case 77: // M to open the embed window
|
||||
embed_window.toggle()
|
||||
@@ -188,7 +298,7 @@ const keyboard_event = evt => {
|
||||
<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" : ""}>
|
||||
<a href="/" bind:this={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">
|
||||
@@ -208,12 +318,7 @@ const keyboard_event = evt => {
|
||||
|
||||
<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>
|
||||
<FileStats file={current_file}></FileStats>
|
||||
|
||||
<hr/>
|
||||
<button on:click={download} class="toolbar_button button_full_width">
|
||||
@@ -221,17 +326,29 @@ const keyboard_event = evt => {
|
||||
<span>Download</span>
|
||||
</button>
|
||||
{#if is_list}
|
||||
<button class="toolbar_button button_full_width">
|
||||
<button on:click={download_list} 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">
|
||||
<button on:click={copy_url}
|
||||
class="toolbar_button button_full_width"
|
||||
class:button_highlight={copy_url_status === "copied"}
|
||||
class:button_red={copy_url_status === "error"}>
|
||||
<i class="icon">content_copy</i>
|
||||
<span><u>C</u>opy link</span>
|
||||
<span>
|
||||
{#if copy_url_status === "copied"}
|
||||
Copied!
|
||||
{:else if copy_url_status === "error"}
|
||||
Error!
|
||||
{:else}
|
||||
<u>C</u>opy link
|
||||
{/if}
|
||||
</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
|
||||
<i class="icon">share</i>
|
||||
<span>Share</span>
|
||||
</button>
|
||||
<button class="toolbar_button button_full_width" on:click={qr_window.toggle} class:button_highlight={qr_visible}>
|
||||
<i class="icon">qr_code</i>
|
||||
@@ -257,14 +374,18 @@ const keyboard_event = evt => {
|
||||
<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>
|
||||
{#if current_file.can_edit}
|
||||
<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>
|
||||
{/if}
|
||||
{#if window.user_authenticated && !current_file.can_edit}
|
||||
<button on:click={grab_file} 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>
|
||||
{/if}
|
||||
<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>
|
||||
@@ -295,7 +416,7 @@ const keyboard_event = evt => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="file_preview" class="file_preview checkers" class:toolbar_visible>
|
||||
<div id="file_preview" class="file_preview checkers" class:toolbar_visible class:skyscraper_visible>
|
||||
<FilePreview
|
||||
file={current_file}
|
||||
bind:this={file_preview}
|
||||
@@ -305,38 +426,65 @@ const keyboard_event = evt => {
|
||||
</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>
|
||||
{#if current_file.show_ads && window.viewer_data.user_ads_enabled}
|
||||
<AdSkyscraper on:visibility={e => {skyscraper_visible = e.detail}}></AdSkyscraper>
|
||||
{/if}
|
||||
|
||||
<!-- 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">
|
||||
{#if current_file.show_ads && window.viewer_data.user_ads_enabled}
|
||||
<AdLeaderboard></AdLeaderboard>
|
||||
{:else if !window.viewer_data.user_ads_enabled}
|
||||
<div style="text-align: center; line-height: 1.3em; font-size: 13px;">
|
||||
Thank you for supporting pixeldrain!
|
||||
</div>
|
||||
</div>
|
||||
{:else if !current_file.show_ads}
|
||||
<div style="text-align: center; line-height: 1.3em; font-size: 13px;">
|
||||
The uploader of this file disabled advertisements. You can do the same for <a href="/#pro">only €2 per month</a>!
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<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 bind:this={qr_window} on:is_visible={e => {qr_visible = e.detail}} title="QR code" width="500px">
|
||||
<img src="{window.api_endpoint}/misc/qr?text={encodeURIComponent(window.location.href)}" alt="QR code" style="display: block; width: 100%;"/>
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={edit_window} on:is_visible={e => {edit_visible = e.detail}} title={"Editing "+current_file.name}>
|
||||
Hi!
|
||||
<EditWindow file={current_file} list={current_list}></EditWindow>
|
||||
</Modal>
|
||||
<Modal bind:this={embed_window} on:is_visible={e => {embed_visible = e.detail}} title="Embed file">
|
||||
Hi!
|
||||
|
||||
<Modal bind:this={embed_window} on:is_visible={e => {embed_visible = e.detail}} title="Embed file" width="850px">
|
||||
<EmbedWindow file={current_file} list={current_list}></EmbedWindow>
|
||||
</Modal>
|
||||
<Modal bind:this={report_window} on:is_visible={e => {report_visible = e.detail}} title="Report abuse">
|
||||
Hi!
|
||||
|
||||
<Modal bind:this={report_window} on:is_visible={e => {report_visible = e.detail}} title="Report abuse" width="650px">
|
||||
<ReportWindow file={current_file} list={current_list}></ReportWindow>
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={download_captcha_window} title={captcha_window_title} width="500px">
|
||||
{#if captcha_type === "rate_limit"}
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
{:else if captcha_type === "malware"}
|
||||
<p>
|
||||
According to our scanning systems this file may contain a virus.
|
||||
You can continue downloading this file at your own risk, but you
|
||||
will have to prove that you're a human first.
|
||||
</p>
|
||||
{/if}
|
||||
<br/>
|
||||
<div bind:this={captcha_container} class="captcha_container"></div>
|
||||
</Modal>
|
||||
|
||||
<IntroPopup target={button_home}></IntroPopup>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -400,8 +548,6 @@ const keyboard_event = evt => {
|
||||
}
|
||||
}
|
||||
|
||||
/* List Navigator (row 2) */
|
||||
|
||||
/* File preview area (row 3) */
|
||||
.file_preview_window {
|
||||
flex-grow: 1;
|
||||
@@ -430,18 +576,6 @@ const keyboard_event = evt => {
|
||||
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;
|
||||
@@ -453,11 +587,12 @@ const keyboard_event = evt => {
|
||||
top: 0;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
transition: left 0.5s;
|
||||
transition: left 0.5s, right 0.5s;
|
||||
background-color: var(--layer_2_color);
|
||||
}
|
||||
.toolbar.toolbar_visible { left: 0; }
|
||||
.file_preview.toolbar_visible { left: 8em; }
|
||||
.file_preview.skyscraper_visible { right: 160px; }
|
||||
|
||||
.sharebar {
|
||||
position: absolute;
|
||||
@@ -477,35 +612,6 @@ const keyboard_event = evt => {
|
||||
}
|
||||
.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;
|
||||
@@ -531,13 +637,6 @@ const keyboard_event = evt => {
|
||||
.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 ||
|
||||
@@ -554,65 +653,11 @@ const keyboard_event = evt => {
|
||||
|| MISC COMPONENTS ||
|
||||
===================== */
|
||||
|
||||
.captcha_popup_captcha > div {
|
||||
.captcha_container {
|
||||
text-align: center;
|
||||
}
|
||||
/* global() to silence the unused selector warning */
|
||||
.captcha_container > :global(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>
|
||||
|
78
svelte/src/file_viewer/IntroPopup.svelte
Normal file
78
svelte/src/file_viewer/IntroPopup.svelte
Normal file
@@ -0,0 +1,78 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
let popup
|
||||
let visible = false
|
||||
|
||||
export let target
|
||||
$: set_target(target)
|
||||
const set_target = el => {
|
||||
if (!el) {
|
||||
return
|
||||
}
|
||||
|
||||
move_to_element(el)
|
||||
setTimeout(() => { move_to_element(el) }, 500)
|
||||
}
|
||||
const move_to_element = el => {
|
||||
if (visible && popup) {
|
||||
let rect = el.getBoundingClientRect()
|
||||
popup.style.top = (rect.top + el.offsetHeight + 20) + "px"
|
||||
popup.style.left = (rect.left + (el.clientWidth / 2) - 40) + "px"
|
||||
}
|
||||
}
|
||||
const close = () => {
|
||||
localStorage.setItem("viewer_intro_popup_dismissed", "🍆")
|
||||
visible = false
|
||||
}
|
||||
onMount(() => {
|
||||
if (localStorage.getItem("viewer_intro_popup_dismissed") === "🍆") {
|
||||
return
|
||||
}
|
||||
visible = true
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if visible}
|
||||
<div bind:this={popup} in:fade out:fade class="intro_popup">
|
||||
<h3>Upload your own files here!</h3>
|
||||
<p>
|
||||
On pixeldrain you can share your files with large or small
|
||||
groups of people. The sky is the limit!
|
||||
</p>
|
||||
<button on:click={close} class="close button_highlight round">
|
||||
<i class="icon">check</i> Got it!
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.intro_popup {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
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;
|
||||
}
|
||||
.close {
|
||||
float: right;
|
||||
margin: 0 10px 10px 0;
|
||||
}
|
||||
</style>
|
163
svelte/src/file_viewer/ReportWindow.svelte
Normal file
163
svelte/src/file_viewer/ReportWindow.svelte
Normal file
@@ -0,0 +1,163 @@
|
||||
<script>
|
||||
import Spinner from "../util/Spinner.svelte"
|
||||
|
||||
export let file = {
|
||||
id: "",
|
||||
name: "",
|
||||
get_href: "",
|
||||
}
|
||||
export let list = {
|
||||
id: "",
|
||||
files: [],
|
||||
}
|
||||
|
||||
let abuse_type = ""
|
||||
let single_or_all = "single"
|
||||
let loading = false
|
||||
let results = []
|
||||
|
||||
let submit = async e => {
|
||||
e.preventDefault()
|
||||
|
||||
if (abuse_type === "") {
|
||||
result_success = false
|
||||
result_text = "Please select an abuse type"
|
||||
return
|
||||
}
|
||||
|
||||
loading = true
|
||||
let files = []
|
||||
|
||||
if (single_or_all === "all") {
|
||||
list.files.forEach(file => {
|
||||
files.push(file.id)
|
||||
})
|
||||
} else {
|
||||
files.push(file.id)
|
||||
}
|
||||
|
||||
const form = new FormData()
|
||||
form.append("type", abuse_type)
|
||||
|
||||
results = []
|
||||
|
||||
for (let file_id of files) {
|
||||
try {
|
||||
const resp = await fetch(
|
||||
window.api_endpoint + "/file/" + file_id + "/report_abuse",
|
||||
{ method: "POST", body: form }
|
||||
);
|
||||
if (resp.status >= 400) {
|
||||
let json = await resp.json()
|
||||
if (json.value === "resource_already_exists") {
|
||||
throw "You have already reported this file"
|
||||
} else if (json.value === "file_already_blocked") {
|
||||
throw "This file has already been blocked"
|
||||
} else if (json.value === "multiple_errors") {
|
||||
throw json.errors[0].message
|
||||
}
|
||||
throw json.message
|
||||
}
|
||||
|
||||
results.push({success: true, text: "Report has been sent"})
|
||||
} catch (err) {
|
||||
results.push({success: false, text: "Failed to send report: "+err})
|
||||
}
|
||||
|
||||
results = results
|
||||
}
|
||||
|
||||
loading = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
If you think this file violates pixeldrain's
|
||||
<a href="/about#content-policy">content policy</a> you can
|
||||
report it for moderation with this form. You cannot report
|
||||
copyright abuse with this form, send a formal DMCA notification
|
||||
to the
|
||||
<a href="/about#content-policy">abuse e-mail address</a>
|
||||
instead.
|
||||
</p>
|
||||
<form on:submit={submit} style="width: 100%">
|
||||
<h3>Abuse type</h3>
|
||||
<p>
|
||||
Which type of abuse is shown in this file? Pick the most
|
||||
appropriate one.
|
||||
</p>
|
||||
<label for="type_terrorism">
|
||||
<input type="radio" bind:group={abuse_type} id="type_terrorism" name="abuse_type" value="terrorism">
|
||||
<b>Terrorism</b>: Videos, images or audio fragments showing
|
||||
or promoting the use of intentional violence to achieve
|
||||
political aims.
|
||||
</label>
|
||||
<label for="type_gore">
|
||||
<input type="radio" bind:group={abuse_type} id="type_gore" name="abuse_type" value="gore">
|
||||
<b>Gore</b>: Graphic and shocking videos or images depicting
|
||||
severe harm to humans (or animals).
|
||||
</label>
|
||||
<label for="type_child_abuse">
|
||||
<input type="radio" bind:group={abuse_type} id="type_child_abuse" name="abuse_type" value="child_abuse">
|
||||
<b>Child abuse</b>: Videos or images depicting inappropriate
|
||||
touching or nudity of minors.
|
||||
</label>
|
||||
<label for="type_malware" style="border-bottom: none;">
|
||||
<input type="radio" bind:group={abuse_type} id="type_malware" name="abuse_type" value="malware">
|
||||
<b>Malware</b>: Software programs designed to cause harm to
|
||||
computer systems.
|
||||
</label>
|
||||
|
||||
{#if list.id !== ""}
|
||||
<h3>Report multiple files?</h3>
|
||||
<label for="report_single">
|
||||
<input type="radio" bind:group={single_or_all} id="report_single" name="single_or_all" value="single">
|
||||
Report only the selected file ({file.name})
|
||||
</label>
|
||||
<label for="report_all" style="border-bottom: none;">
|
||||
<input type="radio" bind:group={single_or_all} id="report_all" name="single_or_all" value="all">
|
||||
Report all {list.files.length} files in this list
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<h3>Send</h3>
|
||||
{#if loading}
|
||||
<div class="spinner_container">
|
||||
<Spinner></Spinner>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#each results as result}
|
||||
<div class:highlight_green={result.success} class:highlight_red={!result.success}>
|
||||
{result.text}
|
||||
</div>
|
||||
{/each}
|
||||
<p>
|
||||
Abuse reports are manually reviewed. Normally this shouldn't
|
||||
take more than 24 hours. During busy periods it can take
|
||||
longer.
|
||||
</p>
|
||||
<div style="text-align: right;">
|
||||
<button class="button_highlight abuse_report_submit" type="submit">
|
||||
<i class="icon">send</i> Send
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
label {
|
||||
display: block;
|
||||
border-bottom: 1px var(--layer_2_color_border) solid;
|
||||
padding: 0.5em;
|
||||
}
|
||||
.spinner_container {
|
||||
position: absolute;
|
||||
top: auto;
|
||||
left: 10px;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
z-index: 1000;
|
||||
}
|
||||
</style>
|
@@ -400,7 +400,8 @@ const keydown = (e) => {
|
||||
on:beforeunload={leave_confirmation} />
|
||||
|
||||
<div>
|
||||
{#if window.user.username !== ""}
|
||||
<!-- If the user is logged in and has used more than 50% of their storage space we will show a progress bar -->
|
||||
{#if window.user.username !== "" && window.user.storage_space_used/window.user.subscription.storage_space > 0.5}
|
||||
<div class="limit_width">
|
||||
<StorageProgressBar used={window.user.storage_space_used} total={window.user.subscription.storage_space}></StorageProgressBar>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user