mirror of
https://github.com/mollersuite/monofile.git
synced 2024-11-21 13:36:25 -08:00
feat: ✨ Notifications
button isn't as polished as i'd like
This commit is contained in:
parent
2f043d5eb9
commit
667acf54f2
|
@ -1,22 +1,22 @@
|
|||
<script>
|
||||
import { _void } from "./transition/_void.js";
|
||||
import { _void } from "./transition/_void.js"
|
||||
import { padding_scaleY } from "./transition/padding_scaleY.js"
|
||||
import { fade } from "svelte/transition";
|
||||
import { circIn, circOut } from "svelte/easing";
|
||||
import { serverStats, refresh_stats, account } from "./stores.mjs";
|
||||
import { fade } from "svelte/transition"
|
||||
import { circIn, circOut } from "svelte/easing"
|
||||
import { serverStats, refresh_stats, account } from "./stores.mjs"
|
||||
|
||||
import AttachmentZone from "./uploader/AttachmentZone.svelte";
|
||||
import AttachmentZone from "./uploader/AttachmentZone.svelte"
|
||||
|
||||
// stats
|
||||
|
||||
refresh_stats()
|
||||
|
||||
|
||||
// uploads
|
||||
|
||||
let attachmentZone;
|
||||
let uploads = {};
|
||||
let uploadInProgress = false;
|
||||
|
||||
let attachmentZone
|
||||
let uploads = {}
|
||||
let uploadInProgress = false
|
||||
let notificationPermission = Notification.permission
|
||||
let handle_file_upload = (ev) => {
|
||||
if (ev.detail.type == "clone") {
|
||||
uploads[Math.random().toString().slice(2)] = {
|
||||
|
@ -25,31 +25,31 @@
|
|||
url: ev.detail.url,
|
||||
|
||||
params: {
|
||||
uploadId: ""
|
||||
uploadId: "",
|
||||
},
|
||||
|
||||
uploadStatus:{
|
||||
uploadStatus: {
|
||||
fileId: null,
|
||||
error: null,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
uploads = uploads
|
||||
} else if (ev.detail.type == "upload") {
|
||||
ev.detail.files.forEach((v,x) => {
|
||||
ev.detail.files.forEach((v, x) => {
|
||||
uploads[Math.random().toString().slice(2)] = {
|
||||
type: "upload",
|
||||
name: v.name,
|
||||
file: v,
|
||||
|
||||
params: {
|
||||
uploadId: ""
|
||||
uploadId: "",
|
||||
},
|
||||
|
||||
uploadStatus:{
|
||||
uploadStatus: {
|
||||
fileId: null,
|
||||
error: null,
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -57,18 +57,54 @@
|
|||
}
|
||||
}
|
||||
|
||||
let handle_fetch_promise = (x,prom) => {
|
||||
return prom.then(async (res) => {
|
||||
let txt = await res.text()
|
||||
if (txt.startsWith("[err]")) uploads[x].uploadStatus.error = txt;
|
||||
else {
|
||||
uploads[x].uploadStatus.fileId = txt;
|
||||
|
||||
refresh_stats();
|
||||
}
|
||||
}).catch((err) => {
|
||||
uploads[x].uploadStatus.error = err.toString();
|
||||
})
|
||||
let handle_fetch_promise = (x, prom) => {
|
||||
return prom
|
||||
.then(async (res) => {
|
||||
let txt = await res.text()
|
||||
if (txt.startsWith("[err]")) uploads[x].uploadStatus.error = txt
|
||||
else {
|
||||
uploads[x].uploadStatus.fileId = txt
|
||||
try {
|
||||
new Notification("Upload complete", {
|
||||
body: `View at ${location.origin}/${uploads[x].uploadStatus.fileId}`,
|
||||
actions: [
|
||||
{
|
||||
action: "open",
|
||||
title: "Open",
|
||||
},
|
||||
{
|
||||
action: "delete",
|
||||
title: "Delete",
|
||||
},
|
||||
{
|
||||
action: "copy",
|
||||
title: "Copy",
|
||||
},
|
||||
],
|
||||
}).addEventListener(
|
||||
"notificationclick",
|
||||
({ action }) => {
|
||||
if (action === "open") {
|
||||
open(
|
||||
"/download/" +
|
||||
uploads[x].uploadStatus.fileId
|
||||
)
|
||||
} else if (action == "delete") {
|
||||
alert("TODO")
|
||||
} else {
|
||||
navigator.clipboard.writeText(
|
||||
`${location.origin}/${uploads[x].uploadStatus.fileId}`
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (_) {}
|
||||
refresh_stats()
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
uploads[x].uploadStatus.error = err.toString()
|
||||
})
|
||||
}
|
||||
|
||||
let upload_files = async () => {
|
||||
|
@ -77,112 +113,224 @@
|
|||
let sequential = localStorage.getItem("sequentialMode") == "true"
|
||||
|
||||
// go through all files
|
||||
for (let [x,v] of Object.entries(uploads)) {
|
||||
for (let [x, v] of Object.entries(uploads)) {
|
||||
// quick patch-in to allow for a switch to have everything upload sequentially
|
||||
// switch will have a proper menu option later, for now i'm lazy so it's just gonna be a Secret
|
||||
let hdl = () => {
|
||||
switch(v.type) {
|
||||
switch (v.type) {
|
||||
case "upload":
|
||||
let fd = new FormData()
|
||||
fd.append("file",v.file)
|
||||
fd.append("file", v.file)
|
||||
|
||||
return handle_fetch_promise(x,fetch("/upload",{
|
||||
headers: {
|
||||
"monofile-params": JSON.stringify(v.params)
|
||||
},
|
||||
method: "POST",
|
||||
body: fd
|
||||
}))
|
||||
break
|
||||
case "clone":
|
||||
return handle_fetch_promise(x,fetch("/clone",{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
url: v.url,
|
||||
...v.params
|
||||
return handle_fetch_promise(
|
||||
x,
|
||||
fetch("/upload", {
|
||||
headers: {
|
||||
"monofile-params": JSON.stringify(v.params),
|
||||
},
|
||||
method: "POST",
|
||||
body: fd,
|
||||
})
|
||||
}))
|
||||
break
|
||||
)
|
||||
break
|
||||
case "clone":
|
||||
return handle_fetch_promise(
|
||||
x,
|
||||
fetch("/clone", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
url: v.url,
|
||||
...v.params,
|
||||
}),
|
||||
})
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (sequential) await hdl();
|
||||
else hdl();
|
||||
|
||||
if (sequential) await hdl()
|
||||
else hdl()
|
||||
}
|
||||
}
|
||||
|
||||
// animation
|
||||
|
||||
|
||||
function fileTransition(node) {
|
||||
return {
|
||||
duration: 300,
|
||||
css: t => {
|
||||
css: (t) => {
|
||||
let eased = circOut(t)
|
||||
|
||||
return `
|
||||
height: ${eased*(node.offsetHeight-22)}px;
|
||||
padding: ${eased*10}px 10px;
|
||||
height: ${eased * (node.offsetHeight - 22)}px;
|
||||
padding: ${eased * 10}px 10px;
|
||||
`
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div id="uploadWindow">
|
||||
<h1>monofile</h1>
|
||||
<h1>
|
||||
monofile
|
||||
{#if notificationPermission === "default"}
|
||||
<button
|
||||
on:click={() => {
|
||||
Notification.requestPermission().then(
|
||||
(permission) => (notificationPermission = permission)
|
||||
)
|
||||
}}
|
||||
style="float:right"
|
||||
title="Notify me when the upload finishes"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="Notify me when the upload finishes"
|
||||
><path
|
||||
d="M9.042 19.003h5.916a3 3 0 0 1-5.916 0Zm2.958-17a7.5 7.5 0 0 1 7.5 7.5v4l1.418 3.16A.95.95 0 0 1 20.052 18h-16.1a.95.95 0 0 1-.867-1.338l1.415-3.16V9.49l.005-.25A7.5 7.5 0 0 1 12 2.004Z"
|
||||
fill="#ffffff"
|
||||
/></svg
|
||||
>
|
||||
</button>
|
||||
{/if}
|
||||
</h1>
|
||||
<p style:color="#999999">
|
||||
<span class="number">{$serverStats.version ? `v${$serverStats.version}` : "•••"}</span> — Discord based file sharing
|
||||
<span class="number"
|
||||
>{$serverStats.version ? `v${$serverStats.version}` : "•••"}</span
|
||||
> — Discord based file sharing
|
||||
</p>
|
||||
|
||||
<div style:min-height="10px" />
|
||||
|
||||
|
||||
<!-- consider splitting the file thing into a separate element maybe -->
|
||||
|
||||
<div class="uploadContainer">
|
||||
{#each Object.entries(uploads) as upload (upload[0])}
|
||||
<!-- container to allow for animate directive -->
|
||||
<div>
|
||||
<div class="file" transition:fileTransition style:border={upload[1].uploadStatus.error ? "1px solid #BB7070" : ""}>
|
||||
<h2>{upload[1].name} <span style:color="#999999" style:font-weight="400">{upload[1].type}{@html upload[1].type == "upload" ? ` (${Math.round(upload[1].file.size/1048576)}MiB)` : ""}</span></h2>
|
||||
|
||||
<div
|
||||
class="file"
|
||||
transition:fileTransition
|
||||
style:border={upload[1].uploadStatus.error
|
||||
? "1px solid #BB7070"
|
||||
: ""}
|
||||
>
|
||||
<h2>
|
||||
{upload[1].name}
|
||||
<span style:color="#999999" style:font-weight="400"
|
||||
>{upload[1].type}{@html upload[1].type == "upload"
|
||||
? ` (${Math.round(
|
||||
upload[1].file.size / 1048576
|
||||
)}MiB)`
|
||||
: ""}</span
|
||||
>
|
||||
</h2>
|
||||
|
||||
{#if upload[1].maximized && !uploadInProgress}
|
||||
<div transition:padding_scaleY|local>
|
||||
<div style:height="10px" />
|
||||
<input placeholder="custom id" type="text" bind:value={ uploads[upload[0]].params.uploadId }>
|
||||
<input
|
||||
placeholder="custom id"
|
||||
type="text"
|
||||
bind:value={uploads[upload[0]].params.uploadId}
|
||||
/>
|
||||
<div style:height="10px" />
|
||||
<div class="buttonContainer">
|
||||
<button on:click={() => {delete uploads[upload[0]];uploads=uploads;}}>
|
||||
<button
|
||||
on:click={() => {
|
||||
delete uploads[upload[0]]
|
||||
uploads = uploads
|
||||
}}
|
||||
>
|
||||
delete
|
||||
</button>
|
||||
<button on:click={() => uploads[upload[0]].maximized = false}>
|
||||
<button
|
||||
on:click={() =>
|
||||
(uploads[upload[0]].maximized = false)}
|
||||
>
|
||||
minimize
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{:else if !uploadInProgress}
|
||||
<button on:click={() => uploads[upload[0]].maximized = true} class="hitbox"></button>
|
||||
<button
|
||||
on:click={() =>
|
||||
(uploads[upload[0]].maximized = true)}
|
||||
class="hitbox"
|
||||
/>
|
||||
{:else}
|
||||
<div transition:padding_scaleY|local class="uploadingContainer">
|
||||
<div
|
||||
transition:padding_scaleY|local
|
||||
class="uploadingContainer"
|
||||
>
|
||||
{#if !upload[1].uploadStatus.fileId}
|
||||
<p in:fade={{duration:300, delay:400, easingFunc:circOut}} out:padding_scaleY={{easingFunc:circIn,op:true}}>{upload[1].uploadStatus.error ?? "Uploading..."}</p>
|
||||
<p
|
||||
in:fade={{
|
||||
duration: 300,
|
||||
delay: 400,
|
||||
easingFunc: circOut,
|
||||
}}
|
||||
out:padding_scaleY={{
|
||||
easingFunc: circIn,
|
||||
op: true,
|
||||
}}
|
||||
>
|
||||
{upload[1].uploadStatus.error ??
|
||||
"Uploading..."}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#if upload[1].uploadStatus.fileId}
|
||||
<div style:height="10px" transition:padding_scaleY />
|
||||
<div
|
||||
style:height="10px"
|
||||
transition:padding_scaleY
|
||||
/>
|
||||
{#if !upload[1].viewingUrl}
|
||||
<div class="buttonContainer" out:_void in:_void={{easingFunc:circOut}}>
|
||||
<button on:click={() => uploads[upload[0]].viewingUrl = true}>
|
||||
<div
|
||||
class="buttonContainer"
|
||||
out:_void
|
||||
in:_void={{ easingFunc: circOut }}
|
||||
>
|
||||
<button
|
||||
on:click={() =>
|
||||
(uploads[
|
||||
upload[0]
|
||||
].viewingUrl = true)}
|
||||
>
|
||||
view url
|
||||
</button>
|
||||
<button on:click={() => navigator.clipboard.writeText(`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`)}>
|
||||
<button
|
||||
on:click={() =>
|
||||
navigator.clipboard.writeText(
|
||||
`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`
|
||||
)}
|
||||
>
|
||||
copy url
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="buttonContainer" out:_void in:_void={{easingFunc:circOut}}>
|
||||
<input type="text" readonly value={`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`} style:flex-basis="80%">
|
||||
<button on:click={() => uploads[upload[0]].viewingUrl = false} style:flex-basis="20%">
|
||||
<div
|
||||
class="buttonContainer"
|
||||
out:_void
|
||||
in:_void={{ easingFunc: circOut }}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value={`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`}
|
||||
style:flex-basis="80%"
|
||||
/>
|
||||
<button
|
||||
on:click={() =>
|
||||
(uploads[
|
||||
upload[0]
|
||||
].viewingUrl = false)}
|
||||
style:flex-basis="20%"
|
||||
>
|
||||
ok
|
||||
</button>
|
||||
</div>
|
||||
|
@ -195,48 +343,73 @@
|
|||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
||||
{#if uploadInProgress == false}
|
||||
|
||||
<!-- if required for upload, check if logged in -->
|
||||
{#if ($serverStats.accounts||{}).requiredForUpload ? !!$account.username : true}
|
||||
|
||||
<AttachmentZone bind:this={attachmentZone} on:addFiles={handle_file_upload}/>
|
||||
<div style:min-height="10px" transition:_void={{rTarg:"height",prop:"min-height"}} />
|
||||
{#if ($serverStats.accounts || {}).requiredForUpload ? !!$account.username : true}
|
||||
<AttachmentZone
|
||||
bind:this={attachmentZone}
|
||||
on:addFiles={handle_file_upload}
|
||||
/>
|
||||
<div
|
||||
style:min-height="10px"
|
||||
transition:_void={{ rTarg: "height", prop: "min-height" }}
|
||||
/>
|
||||
{#if Object.keys(uploads).length > 0}
|
||||
<button in:padding_scaleY={{easingFunc:circOut}} out:_void on:click={upload_files}>upload</button>
|
||||
<div transition:_void={{rTarg:"height",prop:"min-height"}} style:min-height="10px" />
|
||||
<button
|
||||
in:padding_scaleY={{ easingFunc: circOut }}
|
||||
out:_void
|
||||
on:click={upload_files}>upload</button
|
||||
>
|
||||
<div
|
||||
transition:_void={{ rTarg: "height", prop: "min-height" }}
|
||||
style:min-height="10px"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{:else}
|
||||
|
||||
<p transition:_void style:color="#999999" style:text-align="center">Please log in to upload files.</p>
|
||||
<div transition:_void={{rTarg:"height",prop:"min-height"}} style:min-height="10px" />
|
||||
|
||||
<p transition:_void style:color="#999999" style:text-align="center">
|
||||
Please log in to upload files.
|
||||
</p>
|
||||
<div
|
||||
transition:_void={{ rTarg: "height", prop: "min-height" }}
|
||||
style:min-height="10px"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{/if}
|
||||
|
||||
|
||||
<p style:color="#999999" style:text-align="center">
|
||||
Hosting <span class="number" style:font-weight="600">{$serverStats.files || "•••"}</span> files
|
||||
—
|
||||
Maximum filesize is <span class="number" style:font-weight="600">{(($serverStats.maxDiscordFileSize || 0)*($serverStats.maxDiscordFiles || 0))/1048576 || "•••"}MiB</span>
|
||||
Hosting <span class="number" style:font-weight="600"
|
||||
>{$serverStats.files || "•••"}</span
|
||||
>
|
||||
files — Maximum filesize is
|
||||
<span class="number" style:font-weight="600"
|
||||
>{(($serverStats.maxDiscordFileSize || 0) *
|
||||
($serverStats.maxDiscordFiles || 0)) /
|
||||
1048576 || "•••"}MiB</span
|
||||
>
|
||||
<br />
|
||||
</p>
|
||||
|
||||
<p style:color="#999999" style:text-align="center" style:font-size="12px">
|
||||
Made with {Math.floor(Math.random()*10)==0 ? "🐟" : "❤"} by <a href="https://cetera.uk" style:font-size="12px"><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 173.8 275.72"
|
||||
height="16"
|
||||
style="vertical-align:middle"
|
||||
fill="currentColor">
|
||||
<circle cx="37.13" cy="26.43" r="21.6" />
|
||||
<circle cx="34.62" cy="117.87" r="34.62" class="middle" />
|
||||
<circle cx="119.78" cy="130.68" r="21.6" class="middle" />
|
||||
<circle cx="127.16" cy="46.64" r="46.65" />
|
||||
<circle cx="102.68" cy="219.58" r="56.14" class="bottom" />
|
||||
</svg> Etcetera</a> — <a href="https://github.com/mollersuite/monofile" style:font-size="12px">source</a>
|
||||
Made with {Math.floor(Math.random() * 10) == 0 ? "🐟" : "❤"} by
|
||||
<a href="https://cetera.uk" style:font-size="12px"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 173.8 275.72"
|
||||
height="16"
|
||||
style="vertical-align:middle"
|
||||
fill="currentColor"
|
||||
>
|
||||
<circle cx="37.13" cy="26.43" r="21.6" />
|
||||
<circle cx="34.62" cy="117.87" r="34.62" class="middle" />
|
||||
<circle cx="119.78" cy="130.68" r="21.6" class="middle" />
|
||||
<circle cx="127.16" cy="46.64" r="46.65" />
|
||||
<circle cx="102.68" cy="219.58" r="56.14" class="bottom" />
|
||||
</svg> Etcetera</a
|
||||
>
|
||||
—
|
||||
<a href="https://github.com/mollersuite/monofile" style:font-size="12px"
|
||||
>source</a
|
||||
>
|
||||
</p>
|
||||
<div style:height="10px" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue