Implement most of the file viewer in svelte
This commit is contained in:
8
svelte/src/file_viewer.js
Normal file
8
svelte/src/file_viewer.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import App from './file_viewer/FileViewer.svelte';
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById("body"),
|
||||
props: {}
|
||||
});
|
||||
|
||||
export default app;
|
134
svelte/src/file_viewer/DetailsWindow.svelte
Normal file
134
svelte/src/file_viewer/DetailsWindow.svelte
Normal 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 ←</td><td> = View previous item in list</td></tr>
|
||||
<tr><td>d or →</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>
|
101
svelte/src/file_viewer/FilePreview.svelte
Normal file
101
svelte/src/file_viewer/FilePreview.svelte
Normal 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>
|
618
svelte/src/file_viewer/FileViewer.svelte
Normal file
618
svelte/src/file_viewer/FileViewer.svelte
Normal 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 ☑</span>
|
||||
{:else}
|
||||
<span>Shuffle ☐</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>
|
120
svelte/src/file_viewer/ListNavigator.svelte
Normal file
120
svelte/src/file_viewer/ListNavigator.svelte
Normal 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>
|
29
svelte/src/file_viewer/viewers/Abuse.svelte
Normal file
29
svelte/src/file_viewer/viewers/Abuse.svelte
Normal 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>
|
106
svelte/src/file_viewer/viewers/Audio.svelte
Normal file
106
svelte/src/file_viewer/viewers/Audio.svelte
Normal 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>
|
47
svelte/src/file_viewer/viewers/File.svelte
Normal file
47
svelte/src/file_viewer/viewers/File.svelte
Normal 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>
|
91
svelte/src/file_viewer/viewers/Image.svelte
Normal file
91
svelte/src/file_viewer/viewers/Image.svelte
Normal 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>
|
23
svelte/src/file_viewer/viewers/PDF.svelte
Normal file
23
svelte/src/file_viewer/viewers/PDF.svelte
Normal 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>
|
126
svelte/src/file_viewer/viewers/Text.svelte
Normal file
126
svelte/src/file_viewer/viewers/Text.svelte
Normal 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>
|
106
svelte/src/file_viewer/viewers/Video.svelte
Normal file
106
svelte/src/file_viewer/viewers/Video.svelte
Normal 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>
|
@@ -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 ☐</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>
|
@@ -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
|
||||
|
@@ -35,7 +35,7 @@ onMount(() => {
|
||||
datasets: [
|
||||
{
|
||||
label: label,
|
||||
backgroundColor: window.highlight_color,
|
||||
backgroundColor: "#"+window.style.highlightColor,
|
||||
borderWidth: 0,
|
||||
lineTension: 0,
|
||||
fill: true,
|
||||
|
@@ -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;
|
||||
}
|
||||
};
|
||||
|
@@ -7,7 +7,6 @@
|
||||
|
||||
<style>
|
||||
svg {
|
||||
color: var(--highlight_color);
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user