Rewrite home page in svelte
This commit is contained in:
@@ -487,6 +487,7 @@ select:disabled , select.disabled {
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
transition: none;
|
transition: none;
|
||||||
padding: 4px 5px 4px 5px;
|
padding: 4px 5px 4px 5px;
|
||||||
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dropdown list of the select tag */
|
/* Dropdown list of the select tag */
|
||||||
|
@@ -10,53 +10,6 @@
|
|||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto 50px auto;
|
margin: 0 auto 50px auto;
|
||||||
}
|
}
|
||||||
.big_button{
|
|
||||||
width: 40%;
|
|
||||||
min-width: 250px;
|
|
||||||
max-width: 400px;
|
|
||||||
margin: 10px !important;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 1.8em;
|
|
||||||
}
|
|
||||||
.instruction_highlight {
|
|
||||||
border-top: 1px solid var(--layer_2_color_border);
|
|
||||||
border-bottom: 1px solid var(--layer_2_color_border);
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 1.5em 0;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
.big_number {
|
|
||||||
font-size: 1.5em;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1em;
|
|
||||||
text-align: center;
|
|
||||||
display: inline-block;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: var(--highlight_color);
|
|
||||||
color: var(--highlight_text_color);
|
|
||||||
border-radius: 30px;
|
|
||||||
padding: 0.15em;
|
|
||||||
margin-right: 0.4em;
|
|
||||||
width: 1.4em;
|
|
||||||
height: 1.4em;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.instruction_text {
|
|
||||||
margin: 0.1em;
|
|
||||||
font-size: 1.5em;
|
|
||||||
display: inline;
|
|
||||||
box-sizing: border-box;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.social_button {
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
.social_button > img, .social_button > svg {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
margin: 5px 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feat_table {
|
.feat_table {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -116,6 +69,10 @@
|
|||||||
|
|
||||||
{{template `modal.css`}}
|
{{template `modal.css`}}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script>window.api_endpoint = '{{.APIEndpoint}}';</script>
|
||||||
|
<link rel='stylesheet' href='/res/svelte/home_page.css'>
|
||||||
|
<script defer src='/res/svelte/home_page.js'></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{{template "page_top" .}}
|
{{template "page_top" .}}
|
||||||
@@ -125,80 +82,9 @@
|
|||||||
<img class="header_image" src="/res/img/header_orbitron_wide.png" alt="Header image">
|
<img class="header_image" src="/res/img/header_orbitron_wide.png" alt="Header image">
|
||||||
</picture>
|
</picture>
|
||||||
<br/>
|
<br/>
|
||||||
<div class="page_content" style="padding-top: 0; margin-bottom: 100px;">
|
|
||||||
<div id="instruction_1" class="instruction_highlight" style="margin-top: 0;"><div class="limit_width">
|
|
||||||
<span class="big_number">1</span>
|
|
||||||
<span class="instruction_text">Select files to upload</span>
|
|
||||||
<br/> You can also drop files on this page from your file
|
|
||||||
manager or paste an image from your clipboard
|
|
||||||
</div></div>
|
|
||||||
<input id="file_input_field" type="file" name="file" multiple="multiple"/>
|
|
||||||
<button id="upload_file_button" class="big_button button_highlight">
|
|
||||||
<i class="icon small">cloud_upload</i>
|
|
||||||
<u>U</u>pload Files
|
|
||||||
</button>
|
|
||||||
<button id="upload_text_button" class="big_button button_highlight">
|
|
||||||
<i class="icon small">text_fields</i>
|
|
||||||
Upload <u>T</u>ext
|
|
||||||
</button>
|
|
||||||
<br/>
|
|
||||||
<p>
|
|
||||||
By uploading files to pixeldrain you acknowledge and accept our
|
|
||||||
<a href="/about#content-policy">content policy</a>.
|
|
||||||
<p>
|
|
||||||
|
|
||||||
<div id="instruction_2" class="instruction_highlight">
|
<!-- Svelte element -->
|
||||||
<div class="limit_width">
|
<div id="uploader" class="page_content" style="padding-top: 0; margin-bottom: 100px;"></div>
|
||||||
<span class="big_number">2</span><span class="instruction_text">Wait for the files to finish uploading</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="file_drop_highlight" class="highlight_green" style="display: none;">
|
|
||||||
Gimme gimme gimme!<br/>
|
|
||||||
Drop your files to upload them
|
|
||||||
</div>
|
|
||||||
<div id="uploads_queue"></div>
|
|
||||||
|
|
||||||
<div id="instruction_3" class="instruction_highlight">
|
|
||||||
<div class="limit_width"><span class="big_number">3</span><span class="instruction_text">Share the files</span></div>
|
|
||||||
</div>
|
|
||||||
<div id="instruction_3_after" style="display: none">
|
|
||||||
<div id="navigator_share_button" style="display: inline-block">
|
|
||||||
<button id="btn_social_share" class="social_button">
|
|
||||||
{{template `share.svg` .}}<br/>Share
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<button id="btn_copy_link" class="social_button" style="display: inline-block">
|
|
||||||
{{template `copy.svg` .}}<br/><span><u>C</u>opy link</span>
|
|
||||||
</button>
|
|
||||||
<button id="btn_open_link" class="social_button" style="display: inline-block">
|
|
||||||
{{template `open_in_new.svg` .}}<br/><span><u>O</u>pen link</span>
|
|
||||||
</button>
|
|
||||||
<div id="social_buttons" style="display: inline-block">
|
|
||||||
<button id="btn_social_email" class="social_button">
|
|
||||||
{{template `email.svg` .}}<br/><u>E</u>-Mail
|
|
||||||
</button>
|
|
||||||
<button id="btn_social_twitter" class="social_button">
|
|
||||||
{{template `twitter.svg` .}}<br/>T<u>w</u>itter
|
|
||||||
</button>
|
|
||||||
<button id="btn_social_facebook" class="social_button">
|
|
||||||
{{template `facebook.svg` .}}<br/><u>F</u>acebook
|
|
||||||
</button>
|
|
||||||
<button id="btn_social_reddit" class="social_button">
|
|
||||||
{{template `reddit.svg` .}}<br/><u>R</u>eddit
|
|
||||||
</button>
|
|
||||||
<button id="btn_social_tumblr" class="social_button">
|
|
||||||
{{template `tumblr.svg` .}}<br/>Tu<u>m</u>blr
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<br/><br/>
|
|
||||||
<button id="btn_create_list"><i class="icon">list</i> Create <u>l</u>ist with uploaded files</button>
|
|
||||||
<button id="btn_copy_links"><i class="icon">content_copy</i> Copy <u>a</u>ll links to clipboard</button>
|
|
||||||
<button id="btn_copy_markdown"><i class="icon">content_copy</i> Copy mark<u>d</u>own to clipboard</button>
|
|
||||||
<button id="btn_copy_bbcode"><i class="icon">content_copy</i> Copy <u>B</u>BCode to clipboard</button>
|
|
||||||
<br/>
|
|
||||||
<div id="created_lists"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1>What is pixeldrain?</h1>
|
<h1>What is pixeldrain?</h1>
|
||||||
<div class="page_content"><div class="limit_width">
|
<div class="page_content"><div class="limit_width">
|
||||||
@@ -459,11 +345,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
'use strict';
|
||||||
let apiEndpoint = '{{.APIEndpoint}}';
|
|
||||||
{{template "util.js"}}
|
|
||||||
{{template "Modal.js"}}
|
{{template "Modal.js"}}
|
||||||
{{template "UploadManager.js"}}
|
|
||||||
{{template "homepage.js"}}
|
|
||||||
|
|
||||||
function explainFileExpiry() {
|
function explainFileExpiry() {
|
||||||
let m = new Modal(
|
let m = new Modal(
|
||||||
|
@@ -35,6 +35,7 @@ export default [
|
|||||||
"user_buckets",
|
"user_buckets",
|
||||||
"user_file_manager",
|
"user_file_manager",
|
||||||
"admin_panel",
|
"admin_panel",
|
||||||
|
"home_page",
|
||||||
].map((name, index) => ({
|
].map((name, index) => ({
|
||||||
input: `src/${name}.js`,
|
input: `src/${name}.js`,
|
||||||
output: {
|
output: {
|
||||||
|
8
svelte/src/home_page.js
Normal file
8
svelte/src/home_page.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import App from './home_page/HomePage.svelte';
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
target: document.getElementById("uploader"),
|
||||||
|
props: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
490
svelte/src/home_page/HomePage.svelte
Normal file
490
svelte/src/home_page/HomePage.svelte
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
<script>
|
||||||
|
import UploadProgressBar from "./UploadProgressBar.svelte"
|
||||||
|
import { copy_text, domain_url } from "../util/Util.svelte"
|
||||||
|
import { tick } from "svelte"
|
||||||
|
import Facebook from "../icons/Facebook.svelte"
|
||||||
|
import Reddit from "../icons/Reddit.svelte"
|
||||||
|
import Twitter from "../icons/Twitter.svelte"
|
||||||
|
import Tumblr from "../icons/Tumblr.svelte"
|
||||||
|
|
||||||
|
// === UPLOAD LOGIC ===
|
||||||
|
|
||||||
|
let file_input_field
|
||||||
|
|
||||||
|
const file_input_change = (event) => {
|
||||||
|
// Start uploading the files async
|
||||||
|
upload_files(event.target.files)
|
||||||
|
|
||||||
|
// This resets the file input field
|
||||||
|
file_input_field.nodeValue = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
let active_uploads = 0
|
||||||
|
let upload_queue = []
|
||||||
|
|
||||||
|
let state = "idle" // idle, uploading, finished
|
||||||
|
|
||||||
|
const upload_files = async (files) => {
|
||||||
|
// Add files to the queue
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
upload_queue.push({
|
||||||
|
file: files.item(i),
|
||||||
|
name: files.item(i).name,
|
||||||
|
status: "queued",
|
||||||
|
component: null,
|
||||||
|
id: "",
|
||||||
|
on_finished: finish_upload,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reassign array and wait for tick to complete. After the tick is completed
|
||||||
|
// each upload progress bar will have bound itself to its array item
|
||||||
|
upload_queue = upload_queue
|
||||||
|
await tick()
|
||||||
|
|
||||||
|
start_upload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const start_upload = () => {
|
||||||
|
let finished_count = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < upload_queue.length && active_uploads < 3; i++) {
|
||||||
|
if (upload_queue[i].status == "queued") {
|
||||||
|
upload_queue[i].status = "uploading"
|
||||||
|
active_uploads++
|
||||||
|
|
||||||
|
upload_queue[i].component.start()
|
||||||
|
} else if (upload_queue[i].status == "finished") {
|
||||||
|
finished_count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active_uploads === 0 && finished_count != 0) {
|
||||||
|
state = "finished"
|
||||||
|
uploads_finished()
|
||||||
|
} else {
|
||||||
|
state = "uploading"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finish_upload = (file) => {
|
||||||
|
active_uploads--
|
||||||
|
start_upload()
|
||||||
|
}
|
||||||
|
|
||||||
|
// === SHARING BUTTONS ===
|
||||||
|
|
||||||
|
let navigator_share = !!(window.navigator && window.navigator.share)
|
||||||
|
let share_title = ""
|
||||||
|
let share_link = ""
|
||||||
|
|
||||||
|
let btn_upload_text
|
||||||
|
let btn_copy_link
|
||||||
|
let btn_open_link
|
||||||
|
let btn_share_email
|
||||||
|
let btn_share_twitter
|
||||||
|
let btn_share_facebook
|
||||||
|
let btn_share_reddit
|
||||||
|
let btn_share_tumblr
|
||||||
|
let btn_create_list
|
||||||
|
let btn_copy_links
|
||||||
|
let btn_copy_markdown
|
||||||
|
let btn_copy_bbcode
|
||||||
|
|
||||||
|
const uploads_finished = () => {
|
||||||
|
let count = upload_queue.reduce(
|
||||||
|
(acc, curr) => curr.status === "finished" ? acc + 1 : acc, 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (count === 1) {
|
||||||
|
share_title = "Download " + upload_queue[0].name + " here"
|
||||||
|
share_link = domain_url() + "/u/" + upload_queue[0].id
|
||||||
|
} else if (count > 1) {
|
||||||
|
create_list(count+" files", true).then(resp => {
|
||||||
|
console.log("Automatic list ID " + resp.id)
|
||||||
|
share_title = "View a collection of "+count+" files here"
|
||||||
|
share_link = domain_url() + "/l/" + resp.id
|
||||||
|
}).catch(err => {
|
||||||
|
alert("Failed to generate link. Please check your internet connection and try again.\nError: " + err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_list(title, anonymous) {
|
||||||
|
let files = upload_queue.reduce(
|
||||||
|
(acc, curr) => {
|
||||||
|
if (curr.status === "finished") {
|
||||||
|
acc.push({"id": curr.id})
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
return fetch(
|
||||||
|
window.api_endpoint+"/list",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json; charset=UTF-8" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
"title": title,
|
||||||
|
"anonymous": anonymous,
|
||||||
|
"files": files
|
||||||
|
})
|
||||||
|
}
|
||||||
|
).then(resp => {
|
||||||
|
if (!resp.ok) {
|
||||||
|
return Promise.reject("HTTP error: " + resp.status)
|
||||||
|
}
|
||||||
|
return resp.json()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const copy_link = () => {
|
||||||
|
if (copy_text(share_link)) {
|
||||||
|
console.log('Text copied')
|
||||||
|
btn_copy_link.querySelector("span").textContent = "Copied!"
|
||||||
|
btn_copy_link.classList.add("button_highlight")
|
||||||
|
} else {
|
||||||
|
console.log('Copying not supported')
|
||||||
|
btn_copy_link.querySelector("span").textContent = "Failed"
|
||||||
|
btn_copy_link.classList.add("button_red")
|
||||||
|
alert("Your browser does not support copying text.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const open_link = () => window.open(share_link, "_blank")
|
||||||
|
const share_mail = () => window.open("mailto:please@set.address?subject=File%20on%20pixeldrain&body=" + share_link)
|
||||||
|
const share_twitter = () => window.open("https://twitter.com/share?url=" + share_link)
|
||||||
|
const share_facebook = () => window.open('https://www.facebook.com/sharer.php?u=' + share_link)
|
||||||
|
const share_reddit = () => window.open('https://www.reddit.com/submit?url=' + share_link)
|
||||||
|
const share_tumblr = () => window.open('https://www.tumblr.com/share/link?url=' + share_link)
|
||||||
|
const share_navigator = () => {
|
||||||
|
window.navigator.share({ title: "Pixeldrain", text: share_title, url: share_link })
|
||||||
|
}
|
||||||
|
|
||||||
|
let created_lists = []
|
||||||
|
const make_list_button = () => {
|
||||||
|
let count = upload_queue.reduce(
|
||||||
|
(acc, curr) => curr.status === "finished" ? acc + 1 : acc,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
let title = prompt(
|
||||||
|
"You are creating a list containing " + count + " files.\n"
|
||||||
|
+ "What do you want to call it?", "My New Album"
|
||||||
|
)
|
||||||
|
if (title === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
create_list(title, false).then(resp => {
|
||||||
|
created_lists.push({
|
||||||
|
title: title,
|
||||||
|
id: resp.id,
|
||||||
|
link: domain_url()+"/l/"+resp.id,
|
||||||
|
thumbnail: "/api/list/"+resp.id+"/thumbnail",
|
||||||
|
})
|
||||||
|
created_lists = created_lists
|
||||||
|
window.open('/l/' + resp.id, '_blank')
|
||||||
|
}).catch(err => {
|
||||||
|
alert("Failed to create list. Server says this:\n"+err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const get_finished_files = () => {
|
||||||
|
return upload_queue.reduce(
|
||||||
|
(acc, curr) => {
|
||||||
|
if (curr.status === "finished") {
|
||||||
|
acc.push(curr)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const copy_links = () => {
|
||||||
|
// Add the text to the textarea
|
||||||
|
let text = ""
|
||||||
|
let files = get_finished_files()
|
||||||
|
files.forEach(file => {
|
||||||
|
// Example: https://pixeldrain.com/u/abcd1234 Some_file.png
|
||||||
|
text += domain_url() + "/u/" + file.id + " " + file.name + "\n"
|
||||||
|
})
|
||||||
|
if (share_link.includes("/l/")) {
|
||||||
|
text += "\n" + share_link + " All " + files.length + " files\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the selected text
|
||||||
|
if (copy_text(text)) {
|
||||||
|
btn_copy_links.classList.add("button_highlight")
|
||||||
|
btn_copy_links.innerHTML = "Links copied to clipboard!"
|
||||||
|
} else {
|
||||||
|
btn_copy_links.classList.add("button_red")
|
||||||
|
btn_copy_links.innerHTML = "Copying links failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const copy_bbcode = () => {
|
||||||
|
// Add the text to the textarea
|
||||||
|
let text = ""
|
||||||
|
let files = get_finished_files()
|
||||||
|
files.forEach(file => {
|
||||||
|
// Example: [url=https://pixeldrain.com/u/abcd1234]Some_file.png[/url]
|
||||||
|
text += "[url=" + domain_url() + "/u/" + file.id + "]" + file.name + "[/url]\n"
|
||||||
|
})
|
||||||
|
if (share_link.includes("/l/")) {
|
||||||
|
text += "\n[url=" + share_link + "]All " + files.length + " files[/url]\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the selected text
|
||||||
|
if (copy_text(text)) {
|
||||||
|
btn_copy_bbcode.classList.add("button_highlight")
|
||||||
|
btn_copy_bbcode.innerHTML = "BBCode copied to clipboard!"
|
||||||
|
} else {
|
||||||
|
btn_copy_bbcode.classList.add("button_red")
|
||||||
|
btn_copy_bbcode.innerHTML = "Copying links failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const copy_markdown = () => {
|
||||||
|
// Add the text to the textarea
|
||||||
|
let text = ""
|
||||||
|
let files = get_finished_files()
|
||||||
|
files.forEach(file => {
|
||||||
|
// Example: * [Some_file.png](https://pixeldrain.com/u/abcd1234)
|
||||||
|
|
||||||
|
if (files.length > 1) { text += " * " }
|
||||||
|
text += "[" + file.name + "](" + domain_url() + "/u/" + file.id + ")\n"
|
||||||
|
})
|
||||||
|
if (share_link.includes("/l/")) {
|
||||||
|
text += " * [All " + files.length + " files](" + share_link + ")\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the selected text
|
||||||
|
if (copy_text(text)) {
|
||||||
|
btn_copy_markdown.classList.add("button_highlight")
|
||||||
|
btn_copy_markdown.innerHTML = "Markdown copied to clipboard!"
|
||||||
|
} else {
|
||||||
|
btn_copy_markdown.classList.add("button_red")
|
||||||
|
btn_copy_markdown.innerHTML = "Copying links failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dragging = false
|
||||||
|
const drop = (e) => {
|
||||||
|
dragging = false;
|
||||||
|
if (e.dataTransfer && e.dataTransfer.files.length > 0) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
upload_files(e.dataTransfer.files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const paste = (e) => {
|
||||||
|
if (e.clipboardData.files[0]) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
upload_files(e.clipboardData.files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const keydown = (e) => {
|
||||||
|
if (e.ctrlKey || e.altKey || e.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
|
||||||
|
}
|
||||||
|
switch (e.key) {
|
||||||
|
case "u": file_input_field.click(); break
|
||||||
|
case "t": btn_upload_text.click(); break
|
||||||
|
case "c": btn_copy_link.click(); break
|
||||||
|
case "o": btn_open_link.click(); break
|
||||||
|
case "l": btn_create_list.click(); break
|
||||||
|
case "e": btn_share_email.click(); break
|
||||||
|
case "w": btn_share_twitter.click(); break
|
||||||
|
case "f": btn_share_facebook.click(); break
|
||||||
|
case "r": btn_share_reddit.click(); break
|
||||||
|
case "m": btn_share_tumblr.click(); break
|
||||||
|
case "a": btn_copy_links.click(); break
|
||||||
|
case "d": btn_copy_markdown.click(); break
|
||||||
|
case "b": btn_copy_bbcode.click(); break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window
|
||||||
|
on:dragover|preventDefault|stopPropagation={() => { dragging = true }}
|
||||||
|
on:dragenter|preventDefault|stopPropagation={() => { dragging = true }}
|
||||||
|
on:dragleave|preventDefault|stopPropagation={() => { dragging = false }}
|
||||||
|
on:drop={drop}
|
||||||
|
on:paste={paste}
|
||||||
|
on:keydown={keydown} />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="instruction" style="margin-top: 0;">
|
||||||
|
<div class="limit_width">
|
||||||
|
<span class="big_number">1</span>
|
||||||
|
<span class="instruction_text">Select files to upload</span>
|
||||||
|
<br/>
|
||||||
|
You can also drop files on this page from your file
|
||||||
|
manager or paste an image from your clipboard
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input bind:this={file_input_field} on:change={file_input_change} type="file" name="file" multiple="multiple"/>
|
||||||
|
<button on:click={() => { file_input_field.click() }} class="big_button button_highlight">
|
||||||
|
<i class="icon small">cloud_upload</i>
|
||||||
|
<u>U</u>pload Files
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<a bind:this={btn_upload_text} href="/t" id="upload_text_button" class="button big_button button_highlight">
|
||||||
|
<i class="icon small">text_fields</i>
|
||||||
|
Upload <u>T</u>ext
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
<p>
|
||||||
|
By uploading files to pixeldrain you acknowledge and accept our
|
||||||
|
<a href="/about#content-policy">content policy</a>.
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<div class="instruction">
|
||||||
|
<div class="limit_width">
|
||||||
|
<span class="big_number">2</span>
|
||||||
|
<span class="instruction_text">Wait for the files to finish uploading</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="file_drop_highlight" class="highlight_green" class:hide={!dragging}>
|
||||||
|
Gimme gimme gimme!<br/>
|
||||||
|
Drop your files to upload them
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#each upload_queue as file}
|
||||||
|
<UploadProgressBar bind:this={file.component} job={file}></UploadProgressBar>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<div class="instruction">
|
||||||
|
<div class="limit_width">
|
||||||
|
<span class="big_number">3</span>
|
||||||
|
<span class="instruction_text">Share the files</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="social_buttons" class:hide={!navigator_share}>
|
||||||
|
<button id="btn_social_share" on:click={share_navigator} class="social_buttons" disabled={state !== "finished"}>
|
||||||
|
<i class="icon">share</i><br/>
|
||||||
|
Share
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button bind:this={btn_copy_link} on:click={copy_link} class="social_buttons" disabled={state !== "finished"}>
|
||||||
|
<i class="icon">content_copy</i><br/>
|
||||||
|
<span><u>C</u>opy link</span>
|
||||||
|
</button>
|
||||||
|
<button bind:this={btn_open_link} on:click={open_link} class="social_buttons" disabled={state !== "finished"}>
|
||||||
|
<i class="icon">open_in_new</i><br/>
|
||||||
|
<span><u>O</u>pen link</span>
|
||||||
|
</button>
|
||||||
|
<div class="social_buttons" class:hide={navigator_share}>
|
||||||
|
<button bind:this={btn_share_email} on:click={share_mail} class="social_buttons" disabled={state !== "finished"}>
|
||||||
|
<i class="icon">email</i><br/>
|
||||||
|
<u>E</u>-Mail
|
||||||
|
</button>
|
||||||
|
<button bind:this={btn_share_twitter} on:click={share_twitter} class="social_buttons" disabled={state !== "finished"}>
|
||||||
|
<Twitter style="width: 40px; height: 40px; margin: 5px 15px;"></Twitter><br/>
|
||||||
|
T<u>w</u>itter
|
||||||
|
</button>
|
||||||
|
<button bind:this={btn_share_facebook} on:click={share_facebook} class="social_buttons" disabled={state !== "finished"}>
|
||||||
|
<Facebook style="width: 40px; height: 40px; margin: 5px 15px;"></Facebook><br/>
|
||||||
|
<u>F</u>acebook
|
||||||
|
</button>
|
||||||
|
<button bind:this={btn_share_reddit} on:click={share_reddit} class="social_buttons" disabled={state !== "finished"}>
|
||||||
|
<Reddit style="width: 40px; height: 40px; margin: 5px 15px;"></Reddit><br/>
|
||||||
|
<u>R</u>eddit
|
||||||
|
</button>
|
||||||
|
<button bind:this={btn_share_tumblr} on:click={share_tumblr} class="social_buttons" disabled={state !== "finished"}>
|
||||||
|
<Tumblr style="width: 40px; height: 40px; margin: 5px 15px;"></Tumblr><br/>
|
||||||
|
Tu<u>m</u>blr
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<br/><br/>
|
||||||
|
<button bind:this={btn_create_list} on:click={make_list_button} disabled={state !== "finished"}>
|
||||||
|
<i class="icon">list</i> Create <u>l</u>ist with uploaded files
|
||||||
|
</button>
|
||||||
|
<button bind:this={btn_copy_links} on:click={copy_links} disabled={state !== "finished"}>
|
||||||
|
<i class="icon">content_copy</i> Copy <u>a</u>ll links to clipboard
|
||||||
|
</button>
|
||||||
|
<button bind:this={btn_copy_markdown} on:click={copy_markdown} disabled={state !== "finished"}>
|
||||||
|
<i class="icon">content_copy</i> Copy mark<u>d</u>own to clipboard
|
||||||
|
</button>
|
||||||
|
<button bind:this={btn_copy_bbcode} on:click={copy_bbcode} disabled={state !== "finished"}>
|
||||||
|
<i class="icon">content_copy</i> Copy <u>B</u>BCode to clipboard
|
||||||
|
</button>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
{#each created_lists as list}
|
||||||
|
<a class="file_button" href="{list.link}" target="_blank">
|
||||||
|
<img src="/api/list/{list.id}/thumbnail" alt="list icon"/>
|
||||||
|
List created!<br/>
|
||||||
|
{list.title}<br/>
|
||||||
|
<span class="file_button_title">{list.link}</span>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.big_button{
|
||||||
|
width: 40%;
|
||||||
|
min-width: 250px;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 10px !important;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 1.8em;
|
||||||
|
}
|
||||||
|
.instruction {
|
||||||
|
border-top: 1px solid var(--layer_2_color_border);
|
||||||
|
border-bottom: 1px solid var(--layer_2_color_border);
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 1.5em 0;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.big_number {
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1em;
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: var(--highlight_color);
|
||||||
|
color: var(--highlight_text_color);
|
||||||
|
border-radius: 30px;
|
||||||
|
padding: 0.15em;
|
||||||
|
margin-right: 0.4em;
|
||||||
|
width: 1.4em;
|
||||||
|
height: 1.4em;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.instruction_text {
|
||||||
|
margin: 0.1em;
|
||||||
|
font-size: 1.5em;
|
||||||
|
display: inline;
|
||||||
|
box-sizing: border-box;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social_buttons {
|
||||||
|
margin: 5px;
|
||||||
|
display: inline-block
|
||||||
|
}
|
||||||
|
.social_buttons.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.social_buttons > .icon {
|
||||||
|
font-size: 40px;
|
||||||
|
display: inline-block;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin: 5px 15px;
|
||||||
|
}
|
||||||
|
.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
264
svelte/src/home_page/UploadProgressBar.svelte
Normal file
264
svelte/src/home_page/UploadProgressBar.svelte
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
<script>
|
||||||
|
import { domain_url } from "../util/Util.svelte"
|
||||||
|
import { formatDataVolume, formatDuration} from "../util/Formatting.svelte"
|
||||||
|
|
||||||
|
export let job = {}
|
||||||
|
let loaded_size
|
||||||
|
let total_size
|
||||||
|
let file_button
|
||||||
|
let tries = 0
|
||||||
|
let start_time = 0
|
||||||
|
let remaining_time = 0
|
||||||
|
|
||||||
|
let last_progress_time = 0
|
||||||
|
let last_loaded_size = 0
|
||||||
|
let transfer_rate = 0
|
||||||
|
const on_progress = () => {
|
||||||
|
let now = new Date().getTime()
|
||||||
|
|
||||||
|
let prog = loaded_size / total_size
|
||||||
|
let elapsed_time = now - start_time
|
||||||
|
remaining_time = (elapsed_time/prog) - elapsed_time
|
||||||
|
|
||||||
|
// Calculate transfer rate
|
||||||
|
if (last_progress_time != 0) {
|
||||||
|
let new_rate = (1000 / (now - last_progress_time)) * (loaded_size - last_loaded_size)
|
||||||
|
|
||||||
|
// Apply smoothing by mixing it with the previous number 10:1
|
||||||
|
transfer_rate = (transfer_rate * 0.9) + (new_rate * 0.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
last_progress_time = now
|
||||||
|
last_loaded_size = loaded_size
|
||||||
|
|
||||||
|
file_button.style.background = 'linear-gradient(' +
|
||||||
|
'to right, ' +
|
||||||
|
'var(--layer_3_color) 0%, ' +
|
||||||
|
'var(--highlight_color) ' + (prog * 100) + '%, ' +
|
||||||
|
'var(--layer_3_color) ' + ((prog * 100) + 1) + '%)'
|
||||||
|
}
|
||||||
|
|
||||||
|
let href = null
|
||||||
|
let target = null
|
||||||
|
const on_success = (resp) => {
|
||||||
|
job.id = resp.id
|
||||||
|
job.status = "finished"
|
||||||
|
job.on_finished(job)
|
||||||
|
|
||||||
|
add_upload_history(resp.id)
|
||||||
|
|
||||||
|
href = "/u/"+resp.id
|
||||||
|
target = "_blank"
|
||||||
|
|
||||||
|
file_button.style.background = 'var(--layer_3_color)'
|
||||||
|
}
|
||||||
|
|
||||||
|
let error_id = ""
|
||||||
|
let error_reason = ""
|
||||||
|
const on_failure = (status, message) => {
|
||||||
|
error_id = status
|
||||||
|
error_reason = message
|
||||||
|
job.status = "error"
|
||||||
|
file_button.style.background = 'var(--danger_color)'
|
||||||
|
file_button.style.color = 'var(--highlight_text_color)'
|
||||||
|
|
||||||
|
job.on_finished(job)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const start = () => {
|
||||||
|
start_time = new Date().getTime()
|
||||||
|
|
||||||
|
let form = new FormData();
|
||||||
|
form.append('file', job.file, job.name);
|
||||||
|
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
xhr.open("POST", window.api_endpoint+"/file", true);
|
||||||
|
xhr.timeout = 21600000; // 6 hours, to account for slow connections
|
||||||
|
|
||||||
|
xhr.upload.addEventListener("progress", evt => {
|
||||||
|
if (evt.lengthComputable) {
|
||||||
|
loaded_size = evt.loaded
|
||||||
|
total_size = evt.total
|
||||||
|
on_progress()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.onreadystatechange = () => {
|
||||||
|
// readystate 4 means the upload is done
|
||||||
|
if (xhr.readyState !== 4) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xhr.status >= 100 && xhr.status < 400) {
|
||||||
|
// Request is a success
|
||||||
|
on_success(JSON.parse(xhr.response))
|
||||||
|
} else if (xhr.status >= 400) {
|
||||||
|
// Request failed
|
||||||
|
console.log("Upload error. status: " + xhr.status + " response: " + xhr.response);
|
||||||
|
let resp = JSON.parse(xhr.response);
|
||||||
|
|
||||||
|
if (resp.value == "file_too_large" || resp.value == "ip_banned" || tries === 3) {
|
||||||
|
// Permanent failure
|
||||||
|
on_failure(resp.value, resp.message)
|
||||||
|
} else {
|
||||||
|
// Temporary failure, try again in 5 seconds
|
||||||
|
tries++
|
||||||
|
setTimeout(start, 5000)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Request did not arrive
|
||||||
|
if (tries < 3) {
|
||||||
|
// Try again
|
||||||
|
tries++
|
||||||
|
setTimeout(start, 5000)
|
||||||
|
} else {
|
||||||
|
// Give up after three tries
|
||||||
|
on_failure(xhr.responseText, xhr.responseText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.send(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
const add_upload_history = id => {
|
||||||
|
// Make sure the user is not logged in, for privacy. This keeps the
|
||||||
|
// files uploaded while logged in and anonymously uploaded files
|
||||||
|
// separated
|
||||||
|
if (document.cookie.includes("pd_auth_key")) { return; }
|
||||||
|
|
||||||
|
let uploads = localStorage.getItem("uploaded_files");
|
||||||
|
if (uploads === null) { uploads = ""; }
|
||||||
|
|
||||||
|
// Check if there are not too many values stored
|
||||||
|
if (uploads.length > 3600) {
|
||||||
|
// 3600 characters is enough to store 400 file IDs. If we exceed that
|
||||||
|
// number we'll drop the last two items
|
||||||
|
uploads = uploads.substring(
|
||||||
|
uploads.indexOf(",") + 1
|
||||||
|
).substring(
|
||||||
|
uploads.indexOf(",") + 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the new ID
|
||||||
|
localStorage.setItem("uploaded_files", id + "," + uploads);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a bind:this={file_button} class="upload_task" {href} {target}>
|
||||||
|
<div class="thumbnail">
|
||||||
|
{#if job.status === "queued"}
|
||||||
|
<i class="icon">cloud_queue</i>
|
||||||
|
{:else if job.status === "uploading"}
|
||||||
|
<i class="icon">cloud_upload</i>
|
||||||
|
{:else if job.status === "finished"}
|
||||||
|
<img src="/api/file/{job.id}/thumbnail" alt="file thumbnail" />
|
||||||
|
{:else if job.status === "error"}
|
||||||
|
<i class="icon">error</i>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="queue_body">
|
||||||
|
<div class="title">
|
||||||
|
{#if job.status === "error"}
|
||||||
|
{error_reason}
|
||||||
|
{:else}
|
||||||
|
{job.name}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="stats">
|
||||||
|
{#if job.status === "queued"}
|
||||||
|
Queued...
|
||||||
|
{:else if job.status === "uploading"}
|
||||||
|
<div class="stat">
|
||||||
|
{((loaded_size/total_size)*100).toPrecision(3)}%
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
ETA {formatDuration(remaining_time, 0)}
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
{formatDataVolume(transfer_rate, 3)}/s
|
||||||
|
</div>
|
||||||
|
{:else if job.status === "finished"}
|
||||||
|
<span class="file_link">
|
||||||
|
{domain_url() + "/u/" + job.id}
|
||||||
|
</span>
|
||||||
|
{:else if job.status === "error"}
|
||||||
|
{error_id}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.upload_task{
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 420px;
|
||||||
|
max-width: 90%;
|
||||||
|
height: 3.8em;
|
||||||
|
margin: 10px;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: 2px 2px 8px -3px var(--shadow_color);
|
||||||
|
background-color: var(--layer_3_color);
|
||||||
|
color: var(--text_color);
|
||||||
|
word-break: break-all;
|
||||||
|
text-align: left;
|
||||||
|
line-height: 1.2em;
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
transition: box-shadow 0.3s, opacity 2s;
|
||||||
|
white-space: normal;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-decoration: none;
|
||||||
|
vertical-align: top;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.upload_task:hover {
|
||||||
|
box-shadow: 0 0 2px 2px var(--highlight_color), inset 0 0 1px 1px var(--highlight_color);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload_task > .thumbnail {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 3.8em;
|
||||||
|
margin-right: 4px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.upload_task > .thumbnail > img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.upload_task > .thumbnail > i {
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
.upload_task > .queue_body {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.upload_task > .queue_body > .title {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.upload_task > .queue_body > .stats {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 1.4em;
|
||||||
|
border-top: 1px solid var(--layer_3_color_border);
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'monospace';
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
.upload_task > .queue_body > .stats > .stat {
|
||||||
|
flex: 0 1 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file_link{
|
||||||
|
color: var(--highlight_color);
|
||||||
|
}
|
||||||
|
</style>
|
5
svelte/src/icons/Facebook.svelte
Normal file
5
svelte/src/icons/Facebook.svelte
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<script>export let style;</script>
|
||||||
|
|
||||||
|
<svg style={style} xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M18,5H15.5A3.5,3.5 0 0,0 12,8.5V11H10V14H12V21H15V14H18V11H15V9A1,1 0 0,1 16,8H18V5Z" />
|
||||||
|
</svg>
|
5
svelte/src/icons/Reddit.svelte
Normal file
5
svelte/src/icons/Reddit.svelte
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<script>export let style;</script>
|
||||||
|
|
||||||
|
<svg style={style} xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M22,12.14C22,10.92 21,9.96 19.81,9.96C19.22,9.96 18.68,10.19 18.29,10.57C16.79,9.5 14.72,8.79 12.43,8.7L13.43,4L16.7,4.71C16.73,5.53 17.41,6.19 18.25,6.19C19.11,6.19 19.81,5.5 19.81,4.63C19.81,3.77 19.11,3.08 18.25,3.08C17.65,3.08 17.11,3.43 16.86,3.95L13.22,3.18C13.11,3.16 13,3.18 12.93,3.24C12.84,3.29 12.79,3.38 12.77,3.5L11.66,8.72C9.33,8.79 7.23,9.5 5.71,10.58C5.32,10.21 4.78,10 4.19,10C2.97,10 2,10.96 2,12.16C2,13.06 2.54,13.81 3.29,14.15C3.25,14.37 3.24,14.58 3.24,14.81C3.24,18.18 7.16,20.93 12,20.93C16.84,20.93 20.76,18.2 20.76,14.81C20.76,14.6 20.75,14.37 20.71,14.15C21.46,13.81 22,13.04 22,12.14M7,13.7C7,12.84 7.68,12.14 8.54,12.14C9.4,12.14 10.1,12.84 10.1,13.7A1.56,1.56 0 0,1 8.54,15.26C7.68,15.28 7,14.56 7,13.7M15.71,17.84C14.63,18.92 12.59,19 12,19C11.39,19 9.35,18.9 8.29,17.84C8.13,17.68 8.13,17.43 8.29,17.27C8.45,17.11 8.7,17.11 8.86,17.27C9.54,17.95 11,18.18 12,18.18C13,18.18 14.47,17.95 15.14,17.27C15.3,17.11 15.55,17.11 15.71,17.27C15.85,17.43 15.85,17.68 15.71,17.84M15.42,15.28C14.56,15.28 13.86,14.58 13.86,13.72A1.56,1.56 0 0,1 15.42,12.16C16.28,12.16 17,12.86 17,13.72C17,14.56 16.28,15.28 15.42,15.28Z" />
|
||||||
|
</svg>
|
5
svelte/src/icons/Tumblr.svelte
Normal file
5
svelte/src/icons/Tumblr.svelte
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<script>export let style;</script>
|
||||||
|
|
||||||
|
<svg style={style} xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M17,11H13V15.5C13,16.44 13.28,17 14.5,17H17V21C17,21 15.54,21.05 14.17,21.05C10.8,21.05 9.5,19 9.5,16.75V11H7V7C10.07,6.74 10.27,4.5 10.5,3H13V7H17" />
|
||||||
|
</svg>
|
5
svelte/src/icons/Twitter.svelte
Normal file
5
svelte/src/icons/Twitter.svelte
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<script>export let style;</script>
|
||||||
|
|
||||||
|
<svg style={style} xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M22.46,6C21.69,6.35 20.86,6.58 20,6.69C20.88,6.16 21.56,5.32 21.88,4.31C21.05,4.81 20.13,5.16 19.16,5.36C18.37,4.5 17.26,4 16,4C13.65,4 11.73,5.92 11.73,8.29C11.73,8.63 11.77,8.96 11.84,9.27C8.28,9.09 5.11,7.38 3,4.79C2.63,5.42 2.42,6.16 2.42,6.94C2.42,8.43 3.17,9.75 4.33,10.5C3.62,10.5 2.96,10.3 2.38,10C2.38,10 2.38,10 2.38,10.03C2.38,12.11 3.86,13.85 5.82,14.24C5.46,14.34 5.08,14.39 4.69,14.39C4.42,14.39 4.15,14.36 3.89,14.31C4.43,16 6,17.26 7.89,17.29C6.43,18.45 4.58,19.13 2.56,19.13C2.22,19.13 1.88,19.11 1.54,19.07C3.44,20.29 5.7,21 8.12,21C16,21 20.33,14.46 20.33,8.79C20.33,8.6 20.33,8.42 20.32,8.23C21.16,7.63 21.88,6.87 22.46,6Z" />
|
||||||
|
</svg>
|
@@ -36,12 +36,12 @@ const minute = second*60
|
|||||||
const hour = minute*60
|
const hour = minute*60
|
||||||
const day = hour*24
|
const day = hour*24
|
||||||
|
|
||||||
export const formatDuration = (ms) => {
|
export const formatDuration = (ms, decimals) => {
|
||||||
let res = ""
|
let res = ""
|
||||||
if (ms >= day) { res += Math.floor(ms/day) + "d " }
|
if (ms >= day) { res += Math.floor(ms/day) + "d " }
|
||||||
if (ms >= hour) { res += Math.floor((ms%day)/hour) + "h " }
|
if (ms >= hour) { res += Math.floor((ms%day)/hour) + "h " }
|
||||||
if (ms >= minute) { res += Math.floor((ms%hour)/minute) + "m " }
|
if (ms >= minute) { res += Math.floor((ms%hour)/minute) + "m " }
|
||||||
return res + ((ms%minute)/second).toFixed(3) + "s"
|
return res + ((ms%minute)/second).toFixed(decimals) + "s"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatDate = (date, hours, minutes, seconds) => {
|
export const formatDate = (date, hours, minutes, seconds) => {
|
||||||
|
38
svelte/src/util/Util.svelte
Normal file
38
svelte/src/util/Util.svelte
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<script context="module">
|
||||||
|
|
||||||
|
export function print_date(date, hours, minutes, seconds) {
|
||||||
|
let dateStr = date.getFullYear()
|
||||||
|
+ "-" + ("00" + (date.getMonth() + 1)).slice(-2)
|
||||||
|
+ "-" + ("00" + date.getDate()).slice(-2)
|
||||||
|
|
||||||
|
if (hours) { dateStr += " " + ("00" + date.getHours()).slice(-2) }
|
||||||
|
if (minutes) { dateStr += ":" + ("00" + date.getMinutes()).slice(-2) }
|
||||||
|
if (seconds) { dateStr += ":" + ("00" + date.getMinutes()).slice(-2) }
|
||||||
|
return dateStr
|
||||||
|
}
|
||||||
|
|
||||||
|
export function copy_text(text) {
|
||||||
|
// Create a textarea to copy the text from
|
||||||
|
let ta = document.createElement("textarea");
|
||||||
|
ta.setAttribute("readonly", "readonly")
|
||||||
|
ta.style.position = "absolute";
|
||||||
|
ta.style.left = "-9999px";
|
||||||
|
ta.value = text; // Put the text in the textarea
|
||||||
|
|
||||||
|
// Add the textarea to the DOM so it can be seleted by the user
|
||||||
|
document.body.appendChild(ta);
|
||||||
|
ta.select() // Select the contents of the textarea
|
||||||
|
let success = document.execCommand("copy"); // Copy the selected text
|
||||||
|
document.body.removeChild(ta); // Remove the textarea
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function domain_url() {
|
||||||
|
let url = window.location.protocol + "//" + window.location.hostname;
|
||||||
|
if (window.location.port != "") {
|
||||||
|
url = url + ":" + window.location.port;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
Reference in New Issue
Block a user