Add file viewer branding options

This commit is contained in:
2022-02-07 12:00:22 +01:00
parent 1de3bb49c5
commit af82d45c6e
20 changed files with 477 additions and 103 deletions

2
go.mod
View File

@@ -2,6 +2,8 @@ module fornaxian.tech/pixeldrain_web
go 1.17 go 1.17
replace fornaxian.tech/pixeldrain_api_client => ../pixeldrain_api_client
require ( require (
fornaxian.tech/config v0.0.0-20211108212237-6133aed90586 fornaxian.tech/config v0.0.0-20211108212237-6133aed90586
fornaxian.tech/log v0.0.0-20211102185326-552e9b1f8640 fornaxian.tech/log v0.0.0-20211102185326-552e9b1f8640

2
go.sum
View File

@@ -3,8 +3,6 @@ fornaxian.tech/config v0.0.0-20211108212237-6133aed90586/go.mod h1:ULIXF4J1DbBw4
fornaxian.tech/log v0.0.0-20190617093801-1c7ce9a7c9b3/go.mod h1:OyWUNsNPlo5AmlOHvJ4s6WcStQw+9rQyBMwmTz0buEM= fornaxian.tech/log v0.0.0-20190617093801-1c7ce9a7c9b3/go.mod h1:OyWUNsNPlo5AmlOHvJ4s6WcStQw+9rQyBMwmTz0buEM=
fornaxian.tech/log v0.0.0-20211102185326-552e9b1f8640 h1:UPDxJwLRCfh/cv80UMSanzmZ0jIcfS1mcd0Y06HYuLw= fornaxian.tech/log v0.0.0-20211102185326-552e9b1f8640 h1:UPDxJwLRCfh/cv80UMSanzmZ0jIcfS1mcd0Y06HYuLw=
fornaxian.tech/log v0.0.0-20211102185326-552e9b1f8640/go.mod h1:sN82qMToeHhP2u3ehvrcE8y1IudRZJAZO9yG5OBYblo= fornaxian.tech/log v0.0.0-20211102185326-552e9b1f8640/go.mod h1:sN82qMToeHhP2u3ehvrcE8y1IudRZJAZO9yG5OBYblo=
fornaxian.tech/pixeldrain_api_client v0.0.0-20220127185304-6a60644d957e h1:/PLg0AMCPx6Uft5oPb70ogK127/oWrRGadPU0kvqRv8=
fornaxian.tech/pixeldrain_api_client v0.0.0-20220127185304-6a60644d957e/go.mod h1:uajB2ofEsefUtxjvs4m7SDyPVRlfrI3qzCSWcud47hY=
fornaxian.tech/util v0.0.0-20211102152345-9a486dee9787 h1:9ujI8Qi6+FTL/YW6xQAS9DmWDMerHBe8foQvVD/G/i0= fornaxian.tech/util v0.0.0-20211102152345-9a486dee9787 h1:9ujI8Qi6+FTL/YW6xQAS9DmWDMerHBe8foQvVD/G/i0=
fornaxian.tech/util v0.0.0-20211102152345-9a486dee9787/go.mod h1:FqVgfghmxTGR3l9Zx4MOMeZ9KHjiEFl3s3C0BSTvBwk= fornaxian.tech/util v0.0.0-20211102152345-9a486dee9787/go.mod h1:FqVgfghmxTGR3l9Zx4MOMeZ9KHjiEFl3s3C0BSTvBwk=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=

View File

@@ -105,7 +105,7 @@ footer {
.page_navigation { .page_navigation {
position: fixed; position: fixed;
backface-visibility: hidden; backface-visibility: hidden;
width: 18em; width: 17em;
height: 100%; height: 100%;
left: 0; left: 0;
float: left; float: left;
@@ -119,7 +119,7 @@ footer {
position: absolute; position: absolute;
right: 0; right: 0;
height: auto; height: auto;
left: 18em; left: 17em;
min-width: 300px; min-width: 300px;
display: inline-block; display: inline-block;
text-align: center; /* Center the header and body */ text-align: center; /* Center the header and body */
@@ -131,7 +131,7 @@ footer {
padding: 8px; padding: 8px;
transition: left 0.5s; transition: left 0.5s;
} }
@media (max-width: 1000px) { @media (max-width: 1200px) {
.page_navigation { .page_navigation {
left: -18em; left: -18em;
} }
@@ -467,7 +467,9 @@ select:disabled , select.disabled {
cursor: not-allowed; cursor: not-allowed;
} }
button > i, button > i,
.button > i { .button > i,
button > svg,
.button > svg {
vertical-align: middle; vertical-align: middle;
line-height: 1; line-height: 1;
} }

BIN
res/static/misc/amogus.opus Normal file

Binary file not shown.

View File

@@ -60,9 +60,9 @@
background-position: center; background-position: center;
background-size: cover; background-size: cover;
text-align: left; text-align: left;
font-size: 1.1em; font-size: 1.2em;
color: #ffffff; color: #ffffff;
text-shadow: 1px 1px 4px #000000; text-shadow: 1px 1px 3px #000000;
} }
.feat_table > div > div.round_tl { border-top-left-radius: 0.5em; } .feat_table > div > div.round_tl { border-top-left-radius: 0.5em; }
@@ -298,10 +298,10 @@
{{end}} {{end}}
</div> </div>
<div class="feat_pro features_cell round_tr"> <div class="feat_pro features_cell round_tr">
<div><span class="text_highlight">20 GB</span> max file size<br/></div> <div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">180 days</span> file expiry</div> <div><span class="text_highlight">180 days</span> file expiry</div>
<div><span class="text_highlight">2 TB</span> transfer limit<br/></div> <div><span class="text_highlight">2 TB</span> transfer limit</div>
<div><span class="text_highlight">2 TB</span> storage space<br/></div> <div><span class="text_highlight">2 TB</span> storage space</div>
</div> </div>
</div> </div>
<div> <div>
@@ -315,10 +315,16 @@
{{end}} {{end}}
</div> </div>
<div class="feat_pro features_cell"> <div class="feat_pro features_cell">
<div><span class="text_highlight">20 GB</span> max file size<br/></div> <div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">360 days</span> file expiry</div> <div><span class="text_highlight">360 days</span> file expiry</div>
<div><span class="text_highlight">4 TB</span> transfer limit<br/></div> <div><span class="text_highlight">4 TB</span> transfer limit</div>
<div><span class="text_highlight">4 TB</span> storage space<br/></div> <div><span class="text_highlight">4 TB</span> storage space</div>
<div>
<span class="text_highlight">File viewer
branding</span>: Set a custom theme and header,
footer and background images on the download pages
for your files
</div>
</div> </div>
</div> </div>
<div> <div>
@@ -332,10 +338,11 @@
{{end}} {{end}}
</div> </div>
<div class="feat_pro features_cell"> <div class="feat_pro features_cell">
<div><span class="text_highlight">20 GB</span> max file size<br/></div> <div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">Files never expire</span></div> <div><span class="text_highlight">Files never expire</span></div>
<div><span class="text_highlight">8 TB</span> transfer limit<br/></div> <div><span class="text_highlight">8 TB</span> transfer limit</div>
<div><span class="text_highlight">8 TB</span> storage space<br/></div> <div><span class="text_highlight">8 TB</span> storage space</div>
<div><span class="text_highlight">File viewer branding</span></div>
</div> </div>
</div> </div>
<div> <div>
@@ -349,10 +356,11 @@
{{end}} {{end}}
</div> </div>
<div class="feat_pro features_cell"> <div class="feat_pro features_cell">
<div><span class="text_highlight">20 GB</span> max file size<br/></div> <div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">Files never expire</span></div> <div><span class="text_highlight">Files never expire</span></div>
<div><span class="text_highlight">16 TB</span> transfer limit<br/></div> <div><span class="text_highlight">16 TB</span> transfer limit</div>
<div><span class="text_highlight">16 TB</span> storage space<br/></div> <div><span class="text_highlight">16 TB</span> storage space</div>
<div><span class="text_highlight">File viewer branding</span></div>
</div> </div>
</div> </div>
<div> <div>
@@ -366,10 +374,11 @@
{{end}} {{end}}
</div> </div>
<div class="feat_pro features_cell"> <div class="feat_pro features_cell">
<div><span class="text_highlight">20 GB</span> max file size<br/></div> <div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">Files never expire</span></div> <div><span class="text_highlight">Files never expire</span></div>
<div><span class="text_highlight">32 TB</span> transfer limit<br/></div> <div><span class="text_highlight">32 TB</span> transfer limit</div>
<div><span class="text_highlight">32 TB</span> storage space<br/></div> <div><span class="text_highlight">32 TB</span> storage space</div>
<div><span class="text_highlight">File viewer branding</span></div>
</div> </div>
</div> </div>
<div> <div>
@@ -385,10 +394,11 @@
{{end}} {{end}}
</div> </div>
<div class="feat_pro features_cell"> <div class="feat_pro features_cell">
<div><span class="text_highlight">20 GB</span> max file size<br/></div> <div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">Files never expire</span></div> <div><span class="text_highlight">Files never expire</span></div>
<div><span class="text_highlight">48 TB</span> transfer limit<br/></div> <div><span class="text_highlight">48 TB</span> transfer limit</div>
<div><span class="text_highlight">48 TB</span> storage space<br/></div> <div><span class="text_highlight">48 TB</span> storage space</div>
<div><span class="text_highlight">File viewer branding</span></div>
</div> </div>
</div> </div>
<div> <div>
@@ -404,10 +414,11 @@
{{end}} {{end}}
</div> </div>
<div class="feat_pro features_cell"> <div class="feat_pro features_cell">
<div><span class="text_highlight">20 GB</span> max file size<br/></div> <div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">Files never expire</span></div> <div><span class="text_highlight">Files never expire</span></div>
<div><span class="text_highlight">64 TB</span> transfer limit<br/></div> <div><span class="text_highlight">64 TB</span> transfer limit</div>
<div><span class="text_highlight">64 TB</span> storage space<br/></div> <div><span class="text_highlight">64 TB</span> storage space</div>
<div><span class="text_highlight">File viewer branding</span></div>
</div> </div>
</div> </div>
<div> <div>
@@ -423,10 +434,11 @@
{{end}} {{end}}
</div> </div>
<div class="feat_pro features_cell"> <div class="feat_pro features_cell">
<div><span class="text_highlight">20 GB</span> max file size<br/></div> <div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">Files never expire</span></div> <div><span class="text_highlight">Files never expire</span></div>
<div><span class="text_highlight">96 TB</span> transfer limit<br/></div> <div><span class="text_highlight">96 TB</span> transfer limit</div>
<div><span class="text_highlight">96 TB</span> storage space<br/></div> <div><span class="text_highlight">96 TB</span> storage space</div>
<div><span class="text_highlight">File viewer branding</span></div>
</div> </div>
</div> </div>
<div> <div>
@@ -442,10 +454,11 @@
{{end}} {{end}}
</div> </div>
<div class="feat_pro features_cell round_br"> <div class="feat_pro features_cell round_br">
<div><span class="text_highlight">20 GB</span> max file size<br/></div> <div><span class="text_highlight">20 GB</span> max file size</div>
<div><span class="text_highlight">Files never expire</span></div> <div><span class="text_highlight">Files never expire</span></div>
<div><span class="text_highlight">128 TB</span> transfer limit<br/></div> <div><span class="text_highlight">128 TB</span> transfer limit</div>
<div><span class="text_highlight">128 TB</span> storage space<br/></div> <div><span class="text_highlight">128 TB</span> storage space</div>
<div><span class="text_highlight">File viewer branding</span></div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,22 @@
<script>
export let src = ""
export let border_top = false;
</script>
{#if src}
<div class:border_top>
<img class="image" src="{src}" alt="User-provided banner"/>
</div>
{/if}
<style>
.border_top {
border-top: solid 1px var(--layer_2_color_border);
}
.image {
display: block;
margin: auto;
max-height: 100px;
max-width: 100%;
}
</style>

View File

@@ -1,4 +1,5 @@
<script> <script>
import ThemePicker from "../util/ThemePicker.svelte";
import { copy_text, domain_url } from "../util/Util.svelte"; import { copy_text, domain_url } from "../util/Util.svelte";
import { file_type } from "./FileUtilities.svelte"; import { file_type } from "./FileUtilities.svelte";
@@ -33,6 +34,7 @@ let style = ""
let set_style = s => { let set_style = s => {
style = s style = s
embed_iframe() embed_iframe()
update_example()
} }
let embed_iframe = () => { let embed_iframe = () => {
@@ -95,8 +97,17 @@ const copy = () => {
} }
} }
const example = () => { let example = false
preview_area.innerHTML = embed_html const toggle_example = () => {
example = !example
update_example()
}
const update_example = () => {
if (example) {
preview_area.innerHTML = embed_html
} else {
preview_area.innerHTML = ""
}
} }
</script> </script>
@@ -142,38 +153,8 @@ const example = () => {
You can change the pixeldrain theme for your embedded file. Try the You can change the pixeldrain theme for your embedded file. Try the
available themes <a href="/appearance">here</a>. available themes <a href="/appearance">here</a>.
</p> </p>
<div class="center">
<button class:button_highlight={style===""} on:click={() => {set_style("")}}> <ThemePicker on:theme_change={e => set_style(e.detail)}></ThemePicker>
Default
</button>
<button class:button_highlight={style==="classic"} on:click={() => {set_style("classic")}}>
Classic
</button>
<button class:button_highlight={style==="solarized_dark"} on:click={() => {set_style("solarized_dark")}}>
Solarized
</button>
<button class:button_highlight={style==="maroon"} on:click={() => {set_style("maroon")}}>
Maroon
</button>
<button class:button_highlight={style==="hacker"} on:click={() => {set_style("hacker")}}>
Hacker
</button>
<button class:button_highlight={style==="canta"} on:click={() => {set_style("canta")}}>
Canta
</button>
<button class:button_highlight={style==="nord"} on:click={() => {set_style("nord")}}>
Nord
</button>
<button class:button_highlight={style==="snowstorm"} on:click={() => {set_style("snowstorm")}}>
Snowstorm
</button>
<button class:button_highlight={style==="deepsea"} on:click={() => {set_style("deepsea")}}>
Deep sea
</button>
<button class:button_highlight={style==="skeuos"} on:click={() => {set_style("skeuos")}}>
Skeuos
</button>
</div>
{:else} {:else}
<h3>Direct link</h3> <h3>Direct link</h3>
<p> <p>
@@ -201,7 +182,7 @@ const example = () => {
Copy HTML Copy HTML
{/if} {/if}
</button> </button>
<button on:click={example}> <button on:click={toggle_example} class:button_highlight={example}>
<i class="icon">visibility</i> Show example <i class="icon">visibility</i> Show example
</button> </button>
</div> </div>

View File

@@ -5,6 +5,7 @@ import DirectoryElement from "../user_file_manager/DirectoryElement.svelte";
import Modal from "../util/Modal.svelte"; import Modal from "../util/Modal.svelte";
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()
export let multi_select = true
let modal; let modal;
let directory_element; let directory_element;
let input_search; let input_search;
@@ -92,7 +93,7 @@ const keydown = (e) => {
</button> </button>
</div> </div>
<div class="dir_container"> <div class="dir_container">
<DirectoryElement bind:this={directory_element}></DirectoryElement> <DirectoryElement bind:this={directory_element} multi_select={multi_select}></DirectoryElement>
</div> </div>
</Modal> </Modal>

View File

@@ -19,6 +19,7 @@ import Sharebar from "./Sharebar.svelte";
import GalleryView from "./GalleryView.svelte"; import GalleryView from "./GalleryView.svelte";
import Spinner from "../util/Spinner.svelte"; import Spinner from "../util/Spinner.svelte";
import Downloader from "./Downloader.svelte"; import Downloader from "./Downloader.svelte";
import CustomBanner from "./CustomBanner.svelte";
let loading = true let loading = true
let embedded = false let embedded = false
@@ -145,6 +146,10 @@ const open_list = l => {
// correct file is opened // correct file is opened
is_list = true is_list = true
if (l.files.length !== 0) {
apply_customizations(l.files[0])
}
hash_change() hash_change()
} }
const hash_change = () => { const hash_change = () => {
@@ -200,6 +205,8 @@ const open_file_index = async index => {
document.title = file.name+" ~ pixeldrain" document.title = file.name+" ~ pixeldrain"
} }
apply_customizations(file)
// Register a file view // Register a file view
fetch(window.api_endpoint + "/file/" + file.id + "/view", { fetch(window.api_endpoint + "/file/" + file.id + "/view", {
method: "POST", method: "POST",
@@ -215,6 +222,30 @@ const toggle_gallery = () => {
} }
} }
// Premium page customizations. In the gallery view we will use the
// customizations for the first file in the list, else we simply use the
// selected file. In most cases they are all the same so the user won't notice
// any change
let file_preview_background
let custom_header = ""
let custom_background = ""
let custom_footer = ""
const apply_customizations = file => {
if (file.custom_header) {
custom_header = window.api_endpoint+"/file/"+file.custom_header
}
if (file.custom_footer) {
custom_footer = window.api_endpoint+"/file/"+file.custom_footer
}
if (file.custom_background) {
custom_background = window.api_endpoint+"/file/"+file.custom_background
file_preview_background.style.backgroundImage = "url('"+custom_background+"')"
} else {
file_preview_background.style.backgroundImage = ""
}
}
let supports_fullscreen = !!document.documentElement.requestFullscreen let supports_fullscreen = !!document.documentElement.requestFullscreen
let fullscreen = false let fullscreen = false
const toggle_fullscreen = () => { const toggle_fullscreen = () => {
@@ -378,6 +409,8 @@ const keyboard_event = evt => {
</ListNavigator> </ListNavigator>
{/if} {/if}
<CustomBanner src={custom_header} border_top={true}></CustomBanner>
<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>
{#if view === "file"} {#if view === "file"}
@@ -523,7 +556,13 @@ const keyboard_event = evt => {
<br/> <br/>
</div></div></div> </div></div></div>
<div id="file_preview" class="file_preview checkers" class:toolbar_visible class:skyscraper_visible> <div bind:this={file_preview_background}
class="file_preview_container"
class:checkers={!custom_background}
class:custom_background={!!custom_background}
class:toolbar_visible
class:skyscraper_visible
>
{#if view === "file"} {#if view === "file"}
<FilePreview <FilePreview
bind:this={file_preview} bind:this={file_preview}
@@ -550,6 +589,8 @@ const keyboard_event = evt => {
{#if ads_enabled} {#if ads_enabled}
<AdLeaderboard></AdLeaderboard> <AdLeaderboard></AdLeaderboard>
{:else if custom_footer}
<CustomBanner src={custom_footer}></CustomBanner>
{: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!
@@ -660,7 +701,7 @@ const keyboard_event = evt => {
height: auto; height: auto;
margin: 0; margin: 0;
} }
.file_preview { .file_preview_container {
position: absolute; position: absolute;
left: 0; left: 0;
right: 0; right: 0;
@@ -676,6 +717,12 @@ const keyboard_event = evt => {
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;
} }
.file_preview_container.toolbar_visible { left: 8em; }
.file_preview_container.skyscraper_visible { right: 160px; }
.file_preview_container.custom_background {
background-size: cover;
background-position: center;
}
/* Toolbars */ /* Toolbars */
.toolbar { .toolbar {
@@ -692,8 +739,6 @@ const keyboard_event = evt => {
z-index: 1; z-index: 1;
} }
.toolbar.toolbar_visible { left: 0; } .toolbar.toolbar_visible { left: 0; }
.file_preview.toolbar_visible { left: 8em; }
.file_preview.skyscraper_visible { right: 160px; }
/* Workaround to hide the scrollbar in non webkit browsers, it's really ugly' */ /* Workaround to hide the scrollbar in non webkit browsers, it's really ugly' */
.toolbar > div { .toolbar > div {

View File

@@ -156,7 +156,7 @@ const drop = (e, index) => {
{/if} {/if}
</div> </div>
<FilePicker bind:this={file_picker} on:files={e => {add_files(e.detail)}}></FilePicker> <FilePicker bind:this={file_picker} on:files={e => {add_files(e.detail)}} multi_select={true}></FilePicker>
<style> <style>
.gallery{ .gallery{

View File

@@ -91,7 +91,6 @@ const copy_magnet = () => {
</a> </a>
<button <button
on:click={copy_magnet} on:click={copy_magnet}
class="button"
class:button_highlight={copy_magnet_status === "copied"} class:button_highlight={copy_magnet_status === "copied"}
class:button_red={copy_magnet_status === "error"} class:button_red={copy_magnet_status === "error"}
> >

View File

@@ -8,6 +8,7 @@ import Twitter from "../icons/Twitter.svelte"
import Tumblr from "../icons/Tumblr.svelte" import Tumblr from "../icons/Tumblr.svelte"
import { formatDataVolume, formatDuration } from "../util/Formatting.svelte"; import { formatDataVolume, formatDuration } from "../util/Formatting.svelte";
import StorageProgressBar from "../user_home/StorageProgressBar.svelte" import StorageProgressBar from "../user_home/StorageProgressBar.svelte"
import Konami from "../util/Konami.svelte"
// === UPLOAD LOGIC === // === UPLOAD LOGIC ===
@@ -384,6 +385,8 @@ const keydown = (e) => {
on:keydown={keydown} on:keydown={keydown}
on:beforeunload={leave_confirmation} /> on:beforeunload={leave_confirmation} />
<Konami></Konami>
<div> <div>
<!-- If the user is logged in and has used more than 50% of their storage space we will show a progress bar --> <!-- 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} {#if window.user.username !== "" && window.user.storage_space_used/window.user.subscription.storage_space > 0.5}

View File

@@ -259,10 +259,11 @@ const detect_shift = (e) => {
shift_pressed = e.type === "keydown" shift_pressed = e.type === "keydown"
} }
export let multi_select = true
let last_selected_node = -1 let last_selected_node = -1
const node_click = (index) => { const node_click = (index) => {
if (selectionMode) { if (selectionMode) {
if (shift_pressed && last_selected_node != -1) { if (multi_select && shift_pressed && last_selected_node != -1) {
let id_low = last_selected_node let id_low = last_selected_node
let id_high = last_selected_node let id_high = last_selected_node
if (last_selected_node < index) { if (last_selected_node < index) {
@@ -277,6 +278,14 @@ const node_click = (index) => {
} }
} }
} else { } else {
// If multi select is disabled we deselect all other files before
// selecting this one
if (!multi_select) {
for (let i in allFiles) {
allFiles[i].selected = false
}
}
allFiles[index].selected = !allFiles[index].selected allFiles[index].selected = !allFiles[index].selected
} }

View File

@@ -1,4 +1,8 @@
<script> <script>
import { onMount } from "svelte";
import FilePicker from "../file_viewer/FilePicker.svelte";
import SuccessMessage from "../util/SuccessMessage.svelte";
import ThemePicker from "../util/ThemePicker.svelte";
import Form from "./../util/Form.svelte"; import Form from "./../util/Form.svelte";
let password_change = { let password_change = {
@@ -6,7 +10,7 @@ let password_change = {
fields: [ fields: [
{ {
name: "old_password", name: "old_password",
label: "Old password", label: "Current password",
type: "current_password", type: "current_password",
}, { }, {
name: "new_password", name: "new_password",
@@ -135,26 +139,195 @@ let delete_account = {
return {success: true, message: "Success! Your account has been scheduled for deletion in 7 days"} return {success: true, message: "Success! Your account has been scheduled for deletion in 7 days"}
}, },
} }
let success_message
let file_picker
let theme = ""
let currently_selecting = "" // header, background or footer
let header_image_id = ""
let background_image_id = ""
let footer_image_id = ""
let select_file = t => {
currently_selecting = t
file_picker.open()
}
let add_file = files => {
let type = files[0].type
if (type != "image/png" && type != "image/jpeg" && type != "image/gif" && type != "image/webp") {
success_message.set(false, "File must be an image type")
return
}
if (files[0].size > 10e6) {
success_message.set(false, "Files larger than 10 MB are not allowed. Recommended size is below 1 MB")
return
}
if (currently_selecting === "header") {
header_image_id = files[0].id
} else if (currently_selecting === "background") {
background_image_id = files[0].id
} else if (currently_selecting === "footer") {
footer_image_id = files[0].id
}
}
let save = async () => {
const form = new FormData()
form.append("file_theme", theme)
form.append("file_header", header_image_id)
form.append("file_background", background_image_id)
form.append("file_footer", footer_image_id)
const resp = await fetch(
window.api_endpoint+"/user/file_customization",
{ method: "PUT", body: form }
);
if(resp.status >= 400) {
let json = await resp.json()
console.debug(json)
success_message.set(false, json.message)
return
}
success_message.set(true, "Changes saved")
}
onMount(() => {
theme = window.user.custom_file_theme
header_image_id = window.user.custom_file_header
background_image_id = window.user.custom_file_background
footer_image_id = window.user.custom_file_footer
})
</script> </script>
<section> <section>
<h2>Change password</h2> <h2>Account settings</h2>
<div class="highlight_dark"> <h3>Change password</h3>
<Form config={password_change}></Form> <Form config={password_change}></Form>
</div>
<h2>Change e-mail address</h2> <h3>Change e-mail address</h3>
<div class="highlight_dark"> <Form config={email_change}></Form>
<Form config={email_change}></Form>
</div>
<h2>Change name</h2> <h3>Change name</h3>
<div class="highlight_dark"> <Form config={name_change}></Form>
<Form config={name_change}></Form>
</div>
<h2>Delete account</h2> <h3>Delete account</h3>
<div class="highlight_dark"> <Form config={delete_account}></Form>
<Form config={delete_account}></Form>
</div> <h2>File viewer branding</h2>
{#if !window.user.subscription.file_viewer_branding}
<div class="highlight_red">
File viewer branding is not available for your account. Subscribe to
the Persistence plan or higher to enable this feature.
</div>
{:else if !window.user.hotlinking_enabled}
<div class="highlight_red">
To use the branding feature bandwidth sharing needs to be enabled.
Without this the custom images will not be able to load. Enable
bandwidth sharing on the
<a href="/user/subscription">subscription page</a>.
</div>
{/if}
<SuccessMessage bind:this={success_message}></SuccessMessage>
<p>
You can change the appearance of your file viewer pages. The images you
choose here will be loaded each time someone visits one of your files.
The data usage will also be subtracted from your account's data cap.
Keep in mind that large images can take a very long time to load over
cellular connections. I recommend keeping the header and footer images
below 100 kB, and the background image below 1 MB. Allowed image types
are PNG, JPEG, GIF and WebP. If you want to use an animated banner you
should use APNG or WebP. Avoid using animated GIFs as they are very slow
to load.
</p>
<h3>Theme</h3>
<p>
Choose a theme for your download pages. This theme will override the
theme preference of the person viewing the file. Set to 'None' to let
the viewer choose their own theme.
</p>
<ThemePicker on:theme_change={e => theme = e.detail}></ThemePicker>
<h3>Header image</h3>
<p>
Will be shown above the file. Maximum height is 100px. Will be shrunk if
larger.
</p>
<button on:click={() => {select_file("header")}}>
<i class="icon">add_photo_alternate</i>
Select header image
</button>
<button on:click={() => {header_image_id = ""}}>
<i class="icon">close</i>
Remove
</button>
{#if header_image_id}
<div class="highlight_dark">
<img class="banner_preview" src="/api/file/{header_image_id}" alt="Custom file viewer header"/>
</div>
{/if}
<h3>Background image</h3>
<p>
This image will be shown behind the file which is being viewed. I
recommend choosing something dark and not too distracting. Try to keep
the file below 1 MB to not harm page loading times. Using a JPEG image
with a quality value of 60 is usually good enough.
</p>
<button on:click={() => {select_file("background")}}>
<i class="icon">add_photo_alternate</i>
Select background image
</button>
<button on:click={() => {background_image_id = ""}}>
<i class="icon">close</i>
Remove
</button>
{#if background_image_id}
<div class="highlight_dark">
<img class="background_preview" src="/api/file/{background_image_id}" alt="Custom file viewer background"/>
</div>
{/if}
<h3>Footer image</h3>
<p>
Will be shown below the file. Maximum height is 100px. Will be shrunk if
larger.
</p>
<button on:click={() => {select_file("footer")}}>
<i class="icon">add_photo_alternate</i>
Select footer image
</button>
<button on:click={() => {footer_image_id = ""}}>
<i class="icon">close</i>
Remove
</button>
{#if footer_image_id}
<div class="highlight_dark">
<img class="banner_preview" src="/api/file/{footer_image_id}" alt="Custom file viewer footer"/>
</div>
{/if}
<br/>
<br/>
<button on:click={save}>
<i class="icon">save</i> Save
</button>
</section> </section>
<FilePicker bind:this={file_picker} on:files={e => {add_file(e.detail)}} multi_select={false}></FilePicker>
<style>
.banner_preview {
max-height: 100px;
max-width: 100%;
display: block;
margin: auto;
}
.background_preview {
max-height: 200px;
max-width: 100%;
display: block;
margin: auto;
}
</style>

View File

@@ -0,0 +1,31 @@
<script>
let sequence = [
"ArrowUp",
"ArrowUp",
"ArrowDown",
"ArrowDown",
"ArrowLeft",
"ArrowRight",
"ArrowLeft",
"ArrowRight",
"b",
"a",
]
let index = 0
const keypress = e => {
if (e.key === sequence[index]) {
index++
} else {
index = 0
}
if (index === sequence.length) {
index = 0
let audio = new Audio("/res/misc/amogus.opus")
audio.play()
}
}
</script>
<svelte:window on:keydown={keypress} />

View File

@@ -0,0 +1,18 @@
<script>
let s = false
let m = ""
export const set = (success, message) => {
s = success
m = message
}
export const clear = () => {
m = ""
}
</script>
{#if m}
<div class:highlight_green={s} class:highlight_red={!s}>
{m}
</div>
{/if}

View File

@@ -0,0 +1,53 @@
<script>
import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher()
export let theme = ""
let set = s => {
theme = s
dispatch("theme_change", theme)
}
</script>
<div class="center">
<button class:button_highlight={theme===""} on:click={() => {set("")}}>
None
</button>
<button class:button_highlight={theme==="default"} on:click={() => {set("default")}}>
Default (purple)
</button>
<button class:button_highlight={theme==="classic"} on:click={() => {set("classic")}}>
Classic
</button>
<button class:button_highlight={theme==="solarized_dark"} on:click={() => {set("solarized_dark")}}>
Solarized
</button>
<button class:button_highlight={theme==="maroon"} on:click={() => {set("maroon")}}>
Maroon
</button>
<button class:button_highlight={theme==="hacker"} on:click={() => {set("hacker")}}>
Hacker
</button>
<button class:button_highlight={theme==="canta"} on:click={() => {set("canta")}}>
Canta
</button>
<button class:button_highlight={theme==="nord"} on:click={() => {set("nord")}}>
Nord
</button>
<button class:button_highlight={theme==="snowstorm"} on:click={() => {set("snowstorm")}}>
Snowstorm
</button>
<button class:button_highlight={theme==="deepsea"} on:click={() => {set("deepsea")}}>
Deep sea
</button>
<button class:button_highlight={theme==="skeuos"} on:click={() => {set("skeuos")}}>
Skeuos
</button>
</div>
<style>
.center {
text-align: center;
}
</style>

View File

@@ -97,6 +97,8 @@ func (wc *WebController) serveFileViewer(w http.ResponseWriter, r *http.Request,
templateData.Other = vd templateData.Other = vd
fileStyleOverride(templateData, files)
for _, file := range files { for _, file := range files {
if file.AbuseType != "" { if file.AbuseType != "" {
w.WriteHeader(http.StatusUnavailableForLegalReasons) w.WriteHeader(http.StatusUnavailableForLegalReasons)
@@ -157,6 +159,8 @@ func (wc *WebController) serveListViewer(w http.ResponseWriter, r *http.Request,
} }
templateData.Other = vd templateData.Other = vd
fileStyleOverride(templateData, list.Files)
for _, file := range list.Files { for _, file := range list.Files {
if file.AbuseType != "" { if file.AbuseType != "" {
w.WriteHeader(http.StatusUnavailableForLegalReasons) w.WriteHeader(http.StatusUnavailableForLegalReasons)
@@ -175,6 +179,16 @@ func (wc *WebController) serveListViewer(w http.ResponseWriter, r *http.Request,
} }
} }
func fileStyleOverride(td *TemplateData, files []pixelapi.ListFile) {
if len(files) == 0 {
return
}
if files[0].CustomTheme != "" {
td.setStyle(userStyle(files[0].CustomTheme))
}
}
// ServeFileViewerDemo is a dummy API response that responds with info about a // ServeFileViewerDemo is a dummy API response that responds with info about a
// non-existent demo file. This is required by the a-ads ad network to allow for // non-existent demo file. This is required by the a-ads ad network to allow for
// automatic checking of the presence of the ad unit on this page. // automatic checking of the presence of the ad unit on this page.

View File

@@ -21,6 +21,7 @@ import (
// TemplateData is a struct that every template expects when being rendered. In // TemplateData is a struct that every template expects when being rendered. In
// the field Other you can pass your own template-specific variables. // the field Other you can pass your own template-specific variables.
type TemplateData struct { type TemplateData struct {
tpm *TemplateManager
Authenticated bool Authenticated bool
User pixelapi.UserInfo User pixelapi.UserInfo
UserAgent string UserAgent string
@@ -45,15 +46,18 @@ type TemplateData struct {
Form Form Form Form
} }
func (td *TemplateData) setStyle(style pixeldrainStyleSheet) {
td.Style = style
td.UserStyle = template.CSS(style.String())
td.BackgroundPattern = style.Background(td.tpm.tpl)
}
func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request) (t *TemplateData) { func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request) (t *TemplateData) {
var style = userStyle(r)
t = &TemplateData{ t = &TemplateData{
Authenticated: false, tpm: wc.templates,
UserAgent: r.UserAgent(), Authenticated: false,
Style: style, UserAgent: r.UserAgent(),
UserStyle: template.CSS(style.String()), APIEndpoint: template.URL(wc.apiURLExternal),
BackgroundPattern: style.Background(wc.templates.tpl),
APIEndpoint: template.URL(wc.apiURLExternal),
// Use the user's IP address for making requests // Use the user's IP address for making requests
PixelAPI: wc.api.RealIP(util.RemoteAddress(r)).RealAgent(r.UserAgent()), PixelAPI: wc.api.RealIP(util.RemoteAddress(r)).RealAgent(r.UserAgent()),
@@ -63,6 +67,8 @@ func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request)
URLQuery: r.URL.Query(), URLQuery: r.URL.Query(),
} }
t.setStyle(userStyleFromRequest(r))
// If the user is authenticated we'll indentify him and put the user info // If the user is authenticated we'll indentify him and put the user info
// into the templatedata. This is used for putting the username in the menu // into the templatedata. This is used for putting the username in the menu
// and stuff like that // and stuff like that

View File

@@ -9,7 +9,7 @@ import (
"time" "time"
) )
func userStyle(r *http.Request) (s pixeldrainStyleSheet) { func userStyleFromRequest(r *http.Request) (s pixeldrainStyleSheet) {
// Get the chosen style from the URL // Get the chosen style from the URL
var style = r.URL.Query().Get("style") var style = r.URL.Query().Get("style")
@@ -20,6 +20,10 @@ func userStyle(r *http.Request) (s pixeldrainStyleSheet) {
} }
} }
return userStyle(style)
}
func userStyle(style string) (s pixeldrainStyleSheet) {
switch style { switch style {
case "classic": case "classic":
s = pixeldrainClassicStyle s = pixeldrainClassicStyle