File viewer gallery view

This commit is contained in:
2021-11-23 23:45:42 +01:00
parent 3a77f8c13a
commit 55f7cf7307
9 changed files with 533 additions and 169 deletions

View File

@@ -286,7 +286,7 @@ h3 {
} }
h4 { h4 {
font-size: 1.25em; font-size: 1.25em;
font-family: sans-serif; font-family: "light", sans-serif;
border-bottom: 1px var(--layer_2_color_border) solid; border-bottom: 1px var(--layer_2_color_border) solid;
} }
h5 { h5 {

View File

@@ -11,6 +11,7 @@ export let file = {
size: 0, size: 0,
downloads: 0, downloads: 0,
bandwidth_used: 0, bandwidth_used: 0,
bandwidth_used_paid: 0,
description: "", description: "",
timeseries_href: "", timeseries_href: "",
} }
@@ -20,6 +21,9 @@ let view_chart
$: update_charts(file.id) $: update_charts(file.id)
let update_charts = () => { let update_charts = () => {
if (file.id === "") {
return
}
console.log("updating graph") console.log("updating graph")
let today = new Date() let today = new Date()

View File

@@ -1,21 +1,41 @@
<script> <script>
import { createEventDispatcher } from "svelte";
import Spinner from "../util/Spinner.svelte"
let dispatch = createEventDispatcher()
export let file = { export let file = {
id: "", id: "",
name: "", name: "",
get_href: "", get_href: "",
can_edit: false,
}
export let list = {
id: "",
title: "",
files: [],
can_edit: false,
info_href: "",
} }
let file_name = "" let loading = false
let result_success = false let result_success = false
let result_text = "" let result_text = ""
let file_name = ""
$: update_file(file.id) $: update_file(file.id)
let update_file = () => { let update_file = () => {
file_name = file.name file_name = file.name
} }
let list_name = ""
$: update_list(list.id)
let update_list = () => {
list_name = list.title
}
let rename_file = async e => { let rename_file = async e => {
e.preventDefault() e.preventDefault()
loading = true
const form = new FormData() const form = new FormData()
form.append("action", "rename") form.append("action", "rename")
@@ -32,6 +52,9 @@ let rename_file = async e => {
} catch (err) { } catch (err) {
result_success = false result_success = false
result_text = "Could not change file name: " + err result_text = "Could not change file name: " + err
} finally {
loading = false
dispatch("reload")
} }
} }
@@ -39,6 +62,7 @@ let delete_file = async e => {
if (!confirm("Are you sure you want to delete '" + file.name + "'?")) { if (!confirm("Are you sure you want to delete '" + file.name + "'?")) {
return return
} }
loading = true
try { try {
const resp = await fetch(file.get_href, { method: "DELETE" }); const resp = await fetch(file.get_href, { method: "DELETE" });
@@ -51,39 +75,132 @@ let delete_file = async e => {
} catch (err) { } catch (err) {
result_success = false result_success = false
result_text = "Could not delete file: " + err result_text = "Could not delete file: " + err
} finally {
loading = false
}
}
let rename_list = async e => {
e.preventDefault()
loading = true
let listjson = {
title: list_name,
files: [],
}
list.files.forEach(f => {
listjson.files.push({
id: f.id,
})
})
try {
const resp = await fetch(
list.info_href,
{ method: "PUT", body: JSON.stringify(listjson) },
);
if (resp.status >= 400) {
throw (await resp.json()).message
}
result_success = true
result_text = "Album name has been changed. Reload the page to see the changes"
} catch (err) {
result_success = false
result_text = "Could not change album name: " + err
} finally {
loading = false
dispatch("reload")
}
}
let delete_list = async e => {
if (!confirm("Are you sure you want to delete '" + list.title + "'?")) {
return
}
loading = true
try {
const resp = await fetch(list.info_href, { method: "DELETE" });
if (resp.status >= 400) {
throw (await resp.json()).message
}
result_success = true
result_text = "This album has been deleted, you can close the page"
} catch (err) {
result_success = false
result_text = "Could not delete album: " + err
} finally {
loading = false
} }
} }
</script> </script>
<div> <div>
{#if loading}
<div class="spinner_container">
<Spinner></Spinner>
</div>
{/if}
{#if result_text !== ""} {#if result_text !== ""}
<div class:highlight_green={result_success} class:highligt_red={!result_success}> <div class:highlight_green={result_success} class:highligt_red={!result_success}>
{result_text} {result_text}
</div> </div>
{/if} {/if}
<h3>Rename</h3> {#if list.can_edit}
<form on:submit={rename_file} style="display: flex; width: 100%"> <h3>Edit album</h3>
<h4>Rename</h4>
<form on:submit={rename_list} class="indent" style="display: flex;">
<input bind:value={list_name} type="text" style="flex: 1 1 auto"/>
<button type="submit" style="flex: 0 0 auto">
<i class="icon">save</i> Save
</button>
</form>
<h4>Delete</h4>
<p>
When you delete an album the files in the album will not be deleted,
only the album itself.
</p>
<div class="indent">
<button on:click={delete_list} class="button_red">
<i class="icon small">delete</i> Delete album
</button>
</div>
{/if}
{#if file.can_edit}
<h3>Edit file</h3>
<h4>Rename</h4>
<form on:submit={rename_file} class="indent" style="display: flex;">
<input bind:value={file_name} type="text" style="flex: 1 1 auto"/> <input bind:value={file_name} type="text" style="flex: 1 1 auto"/>
<button type="submit" style="flex: 0 0 auto"> <button type="submit" style="flex: 0 0 auto">
<i class="icon">save</i> Save <i class="icon">save</i> Save
</button> </button>
</form> </form>
<h3>Delete</h3> <h4>Delete</h4>
<p> <p>
When you delete a file it cannot be recovered. When you delete a file it cannot be recovered.
Nobody will be able to download it and the link will Nobody will be able to download it and the link will
stop working. The file will also disappear from any stop working. The file will also disappear from any
lists it's contained in. lists it's contained in.
</p> </p>
<div style="text-align: center;"> <div class="indent">
<button on:click={delete_file} class="button_red"> <button on:click={delete_file} class="button_red">
<i class="icon small">delete</i> Delete this file <i class="icon small">delete</i> Delete file
</button> </button>
</div> </div>
{/if}
</div> </div>
<style> <style>
.spinner_container {
position: absolute;
top: 10px;
left: 10px;
height: 100px;
width: 100px;
}
</style> </style>

View File

@@ -15,46 +15,60 @@ import AdHead from "./AdHead.svelte";
import AdLeaderboard from "./AdLeaderboard.svelte"; import AdLeaderboard from "./AdLeaderboard.svelte";
import AdSkyscraper from "./AdSkyscraper.svelte"; import AdSkyscraper from "./AdSkyscraper.svelte";
import Sharebar from "./Sharebar.svelte"; import Sharebar from "./Sharebar.svelte";
import GalleryView from "./GalleryView.svelte";
import Spinner from "../util/Spinner.svelte";
let is_list = false const file_struct = {
let embedded = false
let view_token = ""
let current_file = {
id: "", id: "",
name: "loading...", name: "",
size: 0, size: 0,
bandwidth_used: 0, bandwidth_used: 0,
bandwidth_used_paid: 0,
downloads: 0, downloads: 0,
views: 0, views: 0,
mime_type: "", mime_type: "",
availability: "", availability: "",
abuse_type: "",
show_ads: false, show_ads: false,
can_edit: false, can_edit: false,
get_href: "", get_href: "",
info_href: "",
download_href: "", download_href: "",
icon_href: "", icon_href: "",
} }
let current_list = { const list_struct = {
id: "", id: "",
title: "", title: "",
files: [], files: [],
download_href: "", download_href: "",
info_href: "",
can_edit: false,
} }
let loading = true
let embedded = false
let view_token = ""
let ads_enabled = false
let view = "" // file or gallery
let file = file_struct
let list = list_struct
let is_list = false
let button_home let button_home
let list_navigator let list_navigator
let list_shuffle = false let list_shuffle = false
let toggle_shuffle = () => { let toggle_shuffle = () => {
list_shuffle = !list_shuffle list_shuffle = !list_shuffle
list_navigator.set_shuffle(list_shuffle)
} }
let sharebar let sharebar
let sharebar_visible = false let sharebar_visible = false
let toggle_sharebar = () => { let toggle_sharebar = () => {
if (navigator.share) { if (navigator.share) {
let name = current_file.name let name = file.name
if (is_list) { if (is_list) {
name = current_list.title name = list.title
} }
navigator.share({ navigator.share({
@@ -104,40 +118,109 @@ onMount(() => {
if (viewer_data.type === "list") { if (viewer_data.type === "list") {
open_list(viewer_data.api_response) open_list(viewer_data.api_response)
} else { } else {
open_file(viewer_data.api_response) list.files = [viewer_data.api_response]
open_file_index(0)
} }
})
const open_list = (list) => { ads_enabled = list.files[0].show_ads
list.download_href = window.api_endpoint+"/list/"+list.id+"/zip" loading = false
list.files.forEach(file => { })
file_set_href(file) const reload = async () => {
loading = true
if (is_list) {
try {
const resp = await fetch(list.info_href);
if (resp.status >= 400) {
throw (await resp.json()).message
}
open_list(await resp.json())
} catch (err) {
alert(err)
}
} else {
try {
const resp = await fetch(file.info_href);
if (resp.status >= 400) {
throw (await resp.json()).message
}
list.files = [await resp.json()]
open_file_index(0)
} catch (err) {
alert(err)
}
}
loading = false
}
const open_list = async l => {
l.download_href = window.api_endpoint+"/list/"+l.id+"/zip"
l.info_href = window.api_endpoint+"/list/"+l.id
l.files.forEach(f => {
file_set_href(f)
}) })
current_list = list list = l
// Setting is_list to true activates the ListNavgator, which makes sure the // Setting is_list to true activates the ListNavgator, which makes sure the
// correct file is opened // correct file is opened
is_list = true is_list = true
}
const open_file = (file) => { // Skip to the file defined in the link hash
file_set_href(file) let matches = location.hash.match(new RegExp('item=([^&]*)'))
current_file = file let hashID = parseInt(matches ? matches[1] : null)
if (Number.isInteger(hashID)) {
// The URL contains an item number. Navigate to that item
view = "file"
open_file_index(parseInt(hashID))
} else {
view = "gallery"
}
}
const open_file_index = async index => {
if (index >= list.files.length) {
index = 0
} else if (index < 0) {
index = list.files.length - 1
}
console.debug("received request to open file", index)
file_set_href(list.files[index])
file = list.files[index]
view = "file"
if (is_list) {
// Wait for the ListNavigator to render
await tick()
// Update the URL
location.replace("#item=" + index)
list_navigator.set_item(index)
}
// Register a file view // Register a file view
fetch(window.api_endpoint + "/file/" + current_file.id + "/view", { fetch(window.api_endpoint + "/file/" + file.id + "/view", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" }, headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: "token=" + view_token body: "token=" + view_token
}) })
} }
const file_set_href = f => {
const file_set_href = file => { f.get_href = window.api_endpoint+"/file/"+f.id
file.get_href = window.api_endpoint+"/file/"+file.id f.info_href = window.api_endpoint+"/file/"+f.id+"/info"
file.download_href = window.api_endpoint+"/file/"+file.id+"?download" f.download_href = window.api_endpoint+"/file/"+f.id+"?download"
file.icon_href = window.api_endpoint+"/file/"+file.id+"/thumbnail" f.icon_href = window.api_endpoint+"/file/"+f.id+"/thumbnail"
file.timeseries_href = window.api_endpoint+"/file/"+file.id+"/timeseries" f.timeseries_href = window.api_endpoint+"/file/"+f.id+"/timeseries"
}
const toggle_gallery = () => {
if (view === "gallery") {
open_file_index(0)
} else {
location.replace("#gallery")
view = "gallery"
file = file_struct // Empty the file struct
}
} }
let download_frame let download_frame
@@ -149,12 +232,12 @@ let captcha_container
const download = () => { const download = () => {
if (!window.viewer_data.captcha_key) { if (!window.viewer_data.captcha_key) {
console.debug("Server doesn't support captcha, starting download") console.debug("Server doesn't support captcha, starting download")
download_frame.src = current_file.download_href download_frame.src = file.download_href
return return
} }
if (current_file.availability === "") { if (file.availability === "") {
console.debug("File is available, starting download") console.debug("File is available, starting download")
download_frame.src = current_file.download_href download_frame.src = file.download_href
return return
} }
@@ -164,7 +247,7 @@ const download = () => {
// we trigger the download using the captcha token Google provided us with // we trigger the download using the captcha token Google provided us with
let captcha_complete_callback = token => { let captcha_complete_callback = token => {
// Download the file using the recaptcha token // Download the file using the recaptcha token
download_frame.src = current_file.download_href + "&recaptcha_response=" + token download_frame.src = file.download_href + "&recaptcha_response=" + token
download_captcha_window.hide() download_captcha_window.hide()
} }
@@ -180,10 +263,10 @@ const download = () => {
}) })
} }
if (current_file.availability === "file_rate_limited_captcha_required") { if (file.availability === "file_rate_limited_captcha_required") {
captcha_type = "rate_limit" captcha_type = "rate_limit"
captcha_window_title = "Rate limiting enabled!" captcha_window_title = "Rate limiting enabled!"
} else if (current_file.availability === "virus_detected_captcha_required") { } else if (file.availability === "virus_detected_captcha_required") {
captcha_type = "malware" captcha_type = "malware"
captcha_window_title = "Malware warning!" captcha_window_title = "Malware warning!"
} }
@@ -202,7 +285,7 @@ const download = () => {
} }
const download_list = () => { const download_list = () => {
if (is_list) { if (is_list) {
download_frame.src = current_list.download_href download_frame.src = list.download_href
} }
} }
@@ -233,12 +316,12 @@ const copy_url = () => {
} }
const grab_file = async () => { const grab_file = async () => {
if (!window.user_authenticated || current_file.can_edit) { if (!window.user_authenticated || file.can_edit) {
return return
} }
const form = new FormData() const form = new FormData()
form.append("grab_file", current_file.id) form.append("grab_file", file.id)
try { try {
const resp = await fetch( const resp = await fetch(
@@ -297,7 +380,7 @@ const keyboard_event = evt => {
details_window.toggle() details_window.toggle()
break break
case 69: // E to open the edit window case 69: // E to open the edit window
if (current_file.can_edit) { if (file.can_edit) {
edit_window.toggle() edit_window.toggle()
} }
break break
@@ -321,6 +404,12 @@ const keyboard_event = evt => {
<!-- Head elements for the ads --> <!-- Head elements for the ads -->
<AdHead></AdHead> <AdHead></AdHead>
{#if loading}
<div class="spinner_container">
<Spinner></Spinner>
</div>
{/if}
<div id="headerbar" class="headerbar" class:embedded> <div id="headerbar" class="headerbar" class:embedded>
<button on:click={toolbar_toggle} class="button_toggle_toolbar round" class:button_highlight={toolbar_visible}> <button on:click={toolbar_toggle} class="button_toggle_toolbar round" class:button_highlight={toolbar_visible}>
<i class="icon">menu</i> <i class="icon">menu</i>
@@ -329,8 +418,8 @@ const keyboard_event = evt => {
<PixeldrainLogo style="height: 1.6em; width: 1.6em; margin: 0 4px 0 0;"></PixeldrainLogo> <PixeldrainLogo style="height: 1.6em; width: 1.6em; margin: 0 4px 0 0;"></PixeldrainLogo>
</a> </a>
<div id="file_viewer_headerbar_title" class="file_viewer_headerbar_title"> <div id="file_viewer_headerbar_title" class="file_viewer_headerbar_title">
<div id="list_title">{current_list.title}</div> {#if list.title !== ""}{list.title}<br/>{/if}
<div id="lile_title">{current_file.name}</div> {#if file.name !== ""}{file.name}{/if}
</div> </div>
{#if embedded && supports_fullscreen} {#if embedded && supports_fullscreen}
<button class="round" on:click={toggle_fullscreen}> <button class="round" on:click={toggle_fullscreen}>
@@ -339,27 +428,43 @@ const keyboard_event = evt => {
{/if} {/if}
</div> </div>
{#if is_list} {#if is_list && view === "file"}
<ListNavigator bind:this={list_navigator} files={current_list.files} on:set_file={(e) => { open_file(e.detail) }}></ListNavigator> <ListNavigator
bind:this={list_navigator}
files={list.files}
shuffle={list_shuffle}
on:set_file={e => { open_file_index(e.detail) }}>
</ListNavigator>
{/if} {/if}
<div id="file_preview_window" class="file_preview_window"> <div id="file_preview_window" class="file_preview_window">
<div id="toolbar" class="toolbar" class:toolbar_visible><div><div> <div id="toolbar" class="toolbar" class:toolbar_visible><div><div>
<FileStats file={current_file}></FileStats> {#if is_list}
<button on:click={toggle_gallery} class="toolbar_button button_full_width" class:button_highlight={view === "gallery"}>
<i class="icon">photo_library</i>
Gallery
</button>
<hr/> <hr/>
{#if current_file.abuse_type === ""} {/if}
{#if view === "file"}
<FileStats file={file}></FileStats>
<hr/>
{/if}
{#if file.abuse_type === "" && view === "file"}
<button on:click={download} class="toolbar_button button_full_width"> <button on:click={download} class="toolbar_button button_full_width">
<i class="icon">save</i> <i class="icon">download</i>
<span>Download</span> <span>Download</span>
</button> </button>
{#if is_list} {/if}
{#if file.abuse_type === "" && is_list}
<button on:click={download_list} class="toolbar_button button_full_width"> <button on:click={download_list} class="toolbar_button button_full_width">
<i class="icon">save</i> <i class="icon">download</i>
<span>DL all files</span> <span>DL all files</span>
</button> </button>
{/if} {/if}
{/if}
<button on:click={copy_url} <button on:click={copy_url}
class="toolbar_button button_full_width" class="toolbar_button button_full_width"
class:button_highlight={copy_url_status === "copied"} class:button_highlight={copy_url_status === "copied"}
@@ -375,21 +480,23 @@ const keyboard_event = evt => {
{/if} {/if}
</span> </span>
</button> </button>
<button on:click={toggle_sharebar} class="toolbar_button button_full_width" class:button_highlight={sharebar_visible}> <button on:click={toggle_sharebar} class="toolbar_button button_full_width" class:button_highlight={sharebar_visible}>
<i class="icon">share</i> <i class="icon">share</i>
<span>Share</span> <span>Share</span>
</button> </button>
<button class="toolbar_button button_full_width" on:click={qr_window.toggle} class:button_highlight={qr_visible}> <button class="toolbar_button button_full_width" on:click={qr_window.toggle} class:button_highlight={qr_visible}>
<i class="icon">qr_code</i> <i class="icon">qr_code</i>
<span>QR code</span> <span>QR code</span>
</button> </button>
{#if is_list} {#if is_list}
<button <button
class="toolbar_button button_full_width" class="toolbar_button button_full_width"
title="Randomize the order of the files in this list" title="Randomize the order of the files in this list"
class:button_highlight={list_shuffle} class:button_highlight={list_shuffle}
on:click={toggle_shuffle} on:click={toggle_shuffle}>
>
<i class="icon">shuffle</i> <i class="icon">shuffle</i>
{#if list_shuffle} {#if list_shuffle}
<span>Shuffle&nbsp;&#x2611;</span> <span>Shuffle&nbsp;&#x2611;</span>
@@ -398,46 +505,65 @@ const keyboard_event = evt => {
{/if} {/if}
</button> </button>
{/if} {/if}
{#if view === "file"}
<button class="toolbar_button button_full_width" on:click={details_window.toggle} class:button_highlight={details_visible}> <button class="toolbar_button button_full_width" on:click={details_window.toggle} class:button_highlight={details_visible}>
<i class="icon">help</i> <i class="icon">help</i>
<span>Deta<u>i</u>ls</span> <span>Deta<u>i</u>ls</span>
</button> </button>
{/if}
<hr/> <hr/>
{#if current_file.can_edit}
{#if file.can_edit || list.can_edit}
<button class="toolbar_button button_full_width" on:click={edit_window.toggle} class:button_highlight={edit_visible}> <button class="toolbar_button button_full_width" on:click={edit_window.toggle} class:button_highlight={edit_visible}>
<i class="icon">edit</i> <i class="icon">edit</i>
<span><u>E</u>dit</span> <span><u>E</u>dit</span>
</button> </button>
{/if} {/if}
{#if window.user_authenticated && !current_file.can_edit}
{#if view === "file" && window.user_authenticated && !file.can_edit}
<button on:click={grab_file} class="toolbar_button button_full_width" title="Copy this file to your own pixeldrain account"> <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> <i class="icon">save_alt</i>
<span><u>G</u>rab file</span> <span><u>G</u>rab file</span>
</button> </button>
{/if} {/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}> <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> <i class="icon">code</i>
<span>E<u>m</u>bed</span> <span>E<u>m</u>bed</span>
</button> </button>
{#if view === "file"}
<button class="toolbar_button button_full_width" title="Report abuse in this file" on:click={report_window.toggle} class:button_highlight={report_visible}> <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> <i class="icon">flag</i>
<span>Report</span> <span>Report</span>
</button> </button>
{/if}
<br/> <br/>
</div></div></div> </div></div></div>
<Sharebar bind:this={sharebar}></Sharebar> <Sharebar bind:this={sharebar}></Sharebar>
<div id="file_preview" class="file_preview checkers" class:toolbar_visible class:skyscraper_visible> <div id="file_preview" class="file_preview checkers" class:toolbar_visible class:skyscraper_visible>
{#if view === "file"}
<FilePreview <FilePreview
file={current_file} file={file}
on:download={download} on:download={download}
on:prev={() => { if (list_navigator) { list_navigator.prev() }}} on:prev={() => { if (list_navigator) { list_navigator.prev() }}}
on:next={() => { if (list_navigator) { list_navigator.next() }}}> on:next={() => { if (list_navigator) { list_navigator.next() }}}>
</FilePreview> </FilePreview>
{:else if view === "gallery"}
<GalleryView
list={list}
on:set_file={e => { open_file_index(e.detail) }}
on:reload={reload}
on:loading={e => {loading = e.detail}}>
</GalleryView>
{/if}
</div> </div>
{#if current_file.show_ads} {#if ads_enabled}
<AdSkyscraper on:visibility={e => {skyscraper_visible = e.detail}}></AdSkyscraper> <AdSkyscraper on:visibility={e => {skyscraper_visible = e.detail}}></AdSkyscraper>
{/if} {/if}
@@ -445,36 +571,36 @@ const keyboard_event = evt => {
<iframe bind:this={download_frame} title="File download frame" style="display: none; width: 1px; height: 1px;"></iframe> <iframe bind:this={download_frame} title="File download frame" style="display: none; width: 1px; height: 1px;"></iframe>
</div> </div>
{#if current_file.show_ads} {#if ads_enabled}
<AdLeaderboard></AdLeaderboard> <AdLeaderboard></AdLeaderboard>
{:else if !window.viewer_data.user_ads_enabled && !embedded} {:else if !window.viewer_data.user_ads_enabled && !embedded}
<div style="text-align: center; line-height: 1.3em; font-size: 13px;"> <div style="text-align: center; line-height: 1.3em; font-size: 13px;">
Thank you for supporting pixeldrain! Thank you for supporting pixeldrain!
</div> </div>
{:else if !current_file.show_ads && !embedded} {:else if !ads_enabled && !embedded}
<div style="text-align: center; line-height: 1.3em; font-size: 13px;"> <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>! The uploader of this file disabled advertisements. You can do the same for <a href="/#pro">only €2 per month</a>!
</div> </div>
{/if} {/if}
<Modal bind:this={details_window} on:is_visible={e => {details_visible = e.detail}} title="File details" width="1200px"> <Modal bind:this={details_window} on:is_visible={e => {details_visible = e.detail}} title="File details" width="1200px">
<DetailsWindow file={current_file}></DetailsWindow> <DetailsWindow file={file}></DetailsWindow>
</Modal> </Modal>
<Modal bind:this={qr_window} on:is_visible={e => {qr_visible = e.detail}} title="QR code" width="500px"> <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%;"/> <img src="{window.api_endpoint}/misc/qr?text={encodeURIComponent(window.location.href)}" alt="QR code" style="display: block; width: 100%;"/>
</Modal> </Modal>
<Modal bind:this={edit_window} on:is_visible={e => {edit_visible = e.detail}} title={"Editing "+current_file.name}> <Modal bind:this={edit_window} on:is_visible={e => {edit_visible = e.detail}} title={"Editing "+file.name}>
<EditWindow file={current_file} list={current_list}></EditWindow> <EditWindow file={file} list={list} on:reload={reload}></EditWindow>
</Modal> </Modal>
<Modal bind:this={embed_window} on:is_visible={e => {embed_visible = e.detail}} title="Embed file" width="850px"> <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> <EmbedWindow file={file} list={list}></EmbedWindow>
</Modal> </Modal>
<Modal bind:this={report_window} on:is_visible={e => {report_visible = e.detail}} title="Report abuse" width="800px"> <Modal bind:this={report_window} on:is_visible={e => {report_visible = e.detail}} title="Report abuse" width="800px">
<ReportWindow file={current_file} list={current_list}></ReportWindow> <ReportWindow file={file} list={list}></ReportWindow>
</Modal> </Modal>
<Modal bind:this={download_captcha_window} title={captcha_window_title} width="500px"> <Modal bind:this={download_captcha_window} title={captcha_window_title} width="500px">
@@ -498,17 +624,22 @@ const keyboard_event = evt => {
<IntroPopup target={button_home}></IntroPopup> <IntroPopup target={button_home}></IntroPopup>
</div> </div>
<style> <style>
/* Viewer container */ .spinner_container {
position: absolute;
top: 10px;
right: 10px;
height: 100px;
width: 100px;
z-index: 10000;
}
.file_viewer { .file_viewer {
position: absolute; position: absolute;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
top: 0; width: 100%;
right: 0; height: 100%;
bottom: 0;
left: 0;
overflow: hidden; overflow: hidden;
background-color: var(--layer_2_color); background-color: var(--layer_2_color);
} }
@@ -582,7 +713,7 @@ const keyboard_event = evt => {
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
transition: left 0.5s, right 0.5s; transition: left 0.5s, right 0.5s;
overflow: hidden; overflow: auto;
box-shadow: inset 2px 2px 10px 2px var(--shadow_color); box-shadow: inset 2px 2px 10px 2px var(--shadow_color);
border-radius: 16px; border-radius: 16px;
} }

View File

@@ -0,0 +1,120 @@
<script>
import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher()
export let list = {
id: "",
title: "",
files: [],
download_href: "",
info_href: "",
can_edit: false,
}
const click_file = index => {
dispatch("set_file", index)
}
const delete_file = async index => {
dispatch("loading", true)
let listjson = {
title: list.title,
files: [],
}
list.files.forEach((f, idx) => {
if (idx !== index) {
listjson.files.push({
id: f.id,
})
}
})
try {
const resp = await fetch(
list.info_href,
{ method: "PUT", body: JSON.stringify(listjson) },
);
if (resp.status >= 400) {
throw (await resp.json()).message
}
} catch (err) {
alert("Failed to update album: "+err)
} finally {
dispatch("reload")
}
}
</script>
<div class="gallery">
{#each list.files as file, index}
<div class="file" on:click={() => {click_file(index)}}>
<div
class="icon_container"
class:editing={list.can_edit}
style="background-image: url('{file.icon_href}?width=256&height=256');">
{#if list.can_edit}
<!-- <i class="icon" style="cursor: grab;">drag_indicator</i>
<i class="icon">chevron_left</i>
<i class="icon">chevron_right</i> -->
<i class="icon" on:click|stopPropagation={() => {delete_file(index)}}>delete</i>
{/if}
</div>
{file.name}
</div>
{/each}
<!-- {#if list.can_edit}
<div class="file" style="font-size: 1.5em; padding-top: 2.5em; cursor: pointer;">
<i class="icon">add</i>
<br/>
Add files
</div>
{/if} -->
</div>
<style>
.gallery{
padding: 16px;
box-sizing: border-box;
}
.file{
position: relative;
box-sizing: border-box;
width: 200px;
max-width: 90%;
height: 200px;
margin: 8px;
padding: 0;
overflow: hidden;
border-radius: 8px;
box-shadow: 2px 2px 8px 0 var(--shadow_color);
background-color: var(--layer_3_color);
word-break: break-all;
text-align: center;
line-height: 1.2em;
display: inline-block;
text-overflow: ellipsis;
text-decoration: none;
vertical-align: top;
}
.file:hover {
box-shadow: 0 0 2px 2px var(--highlight_color);
text-decoration: none;
}
.icon_container {
width: 100%;
height: 136px;
background-position: center;
background-size: cover;
font-size: 20px;
text-align: left;
}
.icon_container.editing {
box-shadow: inset 0 60px 40px -40px #000000;
}
.icon_container > i:hover {
color: var(--highlight_color);
cursor: pointer;
}
</style>

View File

@@ -1,5 +1,5 @@
<script> <script>
import { createEventDispatcher, onMount } from "svelte"; import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()
@@ -7,27 +7,17 @@ export let files = []
let file_list_div let file_list_div
let selected_file_index = 0 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 = () => { export const next = () => {
if (shuffle) { if (shuffle) {
rand_item() rand_item()
return return
} }
set_item(selected_file_index+1) select_item_event(selected_file_index+1)
}
export const prev = () => {
select_item_event(selected_file_index-1)
} }
export const prev = () => { set_item(selected_file_index-1) }
let history = [] let history = []
export const rand_item = () => { export const rand_item = () => {
@@ -41,23 +31,15 @@ export const rand_item = () => {
set_item(rand) set_item(rand)
} }
let shuffle = false export let shuffle = false
export const set_shuffle = s => { shuffle = s }
export const set_item = idx => { 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 // Remove the class from the previous selected file
files[selected_file_index].selected = false
selected_file_index = idx selected_file_index = idx
files[idx].selected = true files.forEach((f, i) => {
f.selected = selected_file_index === i
// Update the URL })
location.hash = "item=" + idx files = files
// Add item to history // Add item to history
if(history.length >= (files.length - 6)){ if(history.length >= (files.length - 6)){
@@ -65,8 +47,6 @@ export const set_item = idx => {
} }
history.push(idx) history.push(idx)
dispatch("set_file", files[idx])
// Smoothly scroll the navigator to the correct element // Smoothly scroll the navigator to the correct element
let selected_file = file_list_div.children[idx] let selected_file = file_list_div.children[idx]
let cst = window.getComputedStyle(selected_file) let cst = window.getComputedStyle(selected_file)
@@ -88,11 +68,20 @@ export const set_item = idx => {
} }
animateScroll(start, 0) animateScroll(start, 0)
} }
// select_item signals to the FileViewer that the file needs to be changed. The
// FileViewer then calls set_item if the change has been approved. ListNavigator
// cannot call set_item itself because it will cause a loop.
const select_item_event = idx => {
dispatch("set_file", idx)
}
</script> </script>
<div bind:this={file_list_div} class="list_navigator"> <div bind:this={file_list_div} class="list_navigator">
{#each files as file, index} {#each files as file, index}
<div class="list_item file_button" class:file_button_selected={file.selected} on:click={() => { set_item(index) }}> <div class="list_item file_button"
class:file_button_selected={file.selected}
on:click={() => { select_item_event(index) }}>
<img src={file.icon_href+"?width=48&height=48"} alt={file.name} class="list_item_thumbnail" /> <img src={file.icon_href+"?width=48&height=48"} alt={file.name} class="list_item_thumbnail" />
{file.name} {file.name}
</div> </div>

View File

@@ -23,16 +23,22 @@ export let file = {
<button class="button_highlight" on:click={() => {dispatch("download")}}> <button class="button_highlight" on:click={() => {dispatch("download")}}>
<i class="icon">save</i> Download <i class="icon">save</i> Download
</button> </button>
{#if file.size > 1e9} </div>
{#if file.size > 5e8}
<br/>
<div class="description" style="max-width: 700px; text-align: center;">
<!-- If the file is larger than 500 MB-->
<hr/> <hr/>
Your download speed is currently limited to 4 MiB/s. Downloading this Your download speed is currently limited to 4 MiB/s. Downloading this
file for free will take {formatDuration((file.size/4194304)*1000)}. file for free will take at least
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5291427&cadence=12" class="button"> {formatDuration((file.size/4194304)*1000)}.
Upgrade to Pro You can
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5291427&cadence=12">
upgrade to Pro
</a> </a>
to download at the fastest speed available. to download at the fastest speed available.
{/if}
</div> </div>
{/if}
</div> </div>
<style> <style>

View File

@@ -72,7 +72,7 @@ let download = () => { dispatch("download", {}) }
{:else} {:else}
<h1>This is a video file on pixeldrain</h1> <h1>This is a video file on pixeldrain</h1>
<img src={file.icon_href} alt="Video icon" style="display: inline-block; vertical-align: top;"> <img src={file.icon_href} alt="Video icon" style="display: inline-block; vertical-align: top;">
<div style="display: inline-block; text-align: left; padding-left: 8px; vertical-align: middle; max-width: 600px;"> <div class="description">
The online video player on pixeldrain has been disabled due to The online video player on pixeldrain has been disabled due to
repeated abuse. You can still watch videos online by upgrading to repeated abuse. You can still watch videos online by upgrading to
Pro. Or download the video and watch it locally on your computer. Pro. Or download the video and watch it locally on your computer.
@@ -83,17 +83,23 @@ let download = () => { dispatch("download", {}) }
<button on:click={download}> <button on:click={download}>
<i class="icon">save</i> Download <i class="icon">save</i> Download
</button> </button>
{#if file.size > 1e9} </div>
{#if file.size > 5e8}
<br/>
<div class="description" style="max-width: 700px; text-align: center;">
<!-- If the file is larger than 500 MB-->
<hr/> <hr/>
Your download speed is currently limited to 4 MiB/s. Downloading this Your download speed is currently limited to 4 MiB/s. Downloading this
file for free will take {formatDuration((file.size/4194304)*1000)}. file for free will take at least
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5291427&cadence=12" class="button"> {formatDuration((file.size/4194304)*1000)}.
Upgrade to Pro You can
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5291427&cadence=12">
upgrade to Pro
</a> </a>
to download at the fastest speed available. to download at the fastest speed available.
{/if}
</div> </div>
{/if} {/if}
{/if}
</div> </div>
<style> <style>
@@ -114,4 +120,11 @@ let download = () => { dispatch("download", {}) }
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
} }
.description {
display: inline-block;
text-align: left;
padding-left: 8px;
vertical-align: middle;
max-width: 600px;
}
</style> </style>

View File

@@ -34,19 +34,3 @@ $: frac = used / total
</div> </div>
{/if} {/if}
</div> </div>
<style>
.progress_bar_outer {
display: block;
background-color: var(--layer_1_color);
width: 100%;
height: 3px;
margin: 6px 0 12px 0;
}
.progress_bar_inner {
background-color: var(--highlight_color);
height: 100%;
width: 0;
transition: width 1s;
}
</style>