feat: Notifications

button isn't as polished as i'd like
This commit is contained in:
Jack W. 2023-10-13 22:18:15 -04:00
parent 2f043d5eb9
commit 667acf54f2
No known key found for this signature in database

View file

@ -1,11 +1,11 @@
<script> <script>
import { _void } from "./transition/_void.js"; import { _void } from "./transition/_void.js"
import { padding_scaleY } from "./transition/padding_scaleY.js" import { padding_scaleY } from "./transition/padding_scaleY.js"
import { fade } from "svelte/transition"; import { fade } from "svelte/transition"
import { circIn, circOut } from "svelte/easing"; import { circIn, circOut } from "svelte/easing"
import { serverStats, refresh_stats, account } from "./stores.mjs"; import { serverStats, refresh_stats, account } from "./stores.mjs"
import AttachmentZone from "./uploader/AttachmentZone.svelte"; import AttachmentZone from "./uploader/AttachmentZone.svelte"
// stats // stats
@ -13,10 +13,10 @@
// uploads // uploads
let attachmentZone; let attachmentZone
let uploads = {}; let uploads = {}
let uploadInProgress = false; let uploadInProgress = false
let notificationPermission = Notification.permission
let handle_file_upload = (ev) => { let handle_file_upload = (ev) => {
if (ev.detail.type == "clone") { if (ev.detail.type == "clone") {
uploads[Math.random().toString().slice(2)] = { uploads[Math.random().toString().slice(2)] = {
@ -25,13 +25,13 @@
url: ev.detail.url, url: ev.detail.url,
params: { params: {
uploadId: "" uploadId: "",
}, },
uploadStatus: { uploadStatus: {
fileId: null, fileId: null,
error: null, error: null,
} },
} }
uploads = uploads uploads = uploads
@ -43,13 +43,13 @@
file: v, file: v,
params: { params: {
uploadId: "" uploadId: "",
}, },
uploadStatus: { uploadStatus: {
fileId: null, fileId: null,
error: null, error: null,
} },
} }
}) })
@ -58,16 +58,52 @@
} }
let handle_fetch_promise = (x, prom) => { let handle_fetch_promise = (x, prom) => {
return prom.then(async (res) => { return prom
.then(async (res) => {
let txt = await res.text() let txt = await res.text()
if (txt.startsWith("[err]")) uploads[x].uploadStatus.error = txt; if (txt.startsWith("[err]")) uploads[x].uploadStatus.error = txt
else { else {
uploads[x].uploadStatus.fileId = txt; uploads[x].uploadStatus.fileId = txt
try {
refresh_stats(); 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((err) => { }
uploads[x].uploadStatus.error = err.toString(); )
} catch (_) {}
refresh_stats()
}
})
.catch((err) => {
uploads[x].uploadStatus.error = err.toString()
}) })
} }
@ -86,28 +122,34 @@
let fd = new FormData() let fd = new FormData()
fd.append("file", v.file) fd.append("file", v.file)
return handle_fetch_promise(x,fetch("/upload",{ return handle_fetch_promise(
x,
fetch("/upload", {
headers: { headers: {
"monofile-params": JSON.stringify(v.params) "monofile-params": JSON.stringify(v.params),
}, },
method: "POST", method: "POST",
body: fd body: fd,
})) })
)
break break
case "clone": case "clone":
return handle_fetch_promise(x,fetch("/clone",{ return handle_fetch_promise(
x,
fetch("/clone", {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
url: v.url, url: v.url,
...v.params ...v.params,
}),
}) })
})) )
break break
} }
} }
if (sequential) await hdl(); if (sequential) await hdl()
else hdl(); else hdl()
} }
} }
@ -116,23 +158,50 @@
function fileTransition(node) { function fileTransition(node) {
return { return {
duration: 300, duration: 300,
css: t => { css: (t) => {
let eased = circOut(t) let eased = circOut(t)
return ` return `
height: ${eased * (node.offsetHeight - 22)}px; height: ${eased * (node.offsetHeight - 22)}px;
padding: ${eased * 10}px 10px; padding: ${eased * 10}px 10px;
` `
},
} }
} }
}
</script> </script>
<div id="uploadWindow"> <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"> <p style:color="#999999">
<span class="number">{$serverStats.version ? `v${$serverStats.version}` : "•••"}</span>&nbsp;&nbsp;&nbsp;&nbsp;Discord based file sharing <span class="number"
>{$serverStats.version ? `v${$serverStats.version}` : "•••"}</span
>&nbsp;&nbsp;&nbsp;&nbsp;Discord based file sharing
</p> </p>
<div style:min-height="10px" /> <div style:min-height="10px" />
@ -143,46 +212,125 @@
{#each Object.entries(uploads) as upload (upload[0])} {#each Object.entries(uploads) as upload (upload[0])}
<!-- container to allow for animate directive --> <!-- container to allow for animate directive -->
<div> <div>
<div class="file" transition:fileTransition style:border={upload[1].uploadStatus.error ? "1px solid #BB7070" : ""}> <div
<h2>{upload[1].name} <span style:color="#999999" style:font-weight="400">{upload[1].type}{@html upload[1].type == "upload" ? `&nbsp;(${Math.round(upload[1].file.size/1048576)}MiB)` : ""}</span></h2> 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"
? `&nbsp;(${Math.round(
upload[1].file.size / 1048576
)}MiB)`
: ""}</span
>
</h2>
{#if upload[1].maximized && !uploadInProgress} {#if upload[1].maximized && !uploadInProgress}
<div transition:padding_scaleY|local> <div transition:padding_scaleY|local>
<div style:height="10px" /> <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 style:height="10px" />
<div class="buttonContainer"> <div class="buttonContainer">
<button on:click={() => {delete uploads[upload[0]];uploads=uploads;}}> <button
on:click={() => {
delete uploads[upload[0]]
uploads = uploads
}}
>
delete delete
</button> </button>
<button on:click={() => uploads[upload[0]].maximized = false}> <button
on:click={() =>
(uploads[upload[0]].maximized = false)}
>
minimize minimize
</button> </button>
</div> </div>
</div> </div>
{:else if !uploadInProgress} {: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} {:else}
<div transition:padding_scaleY|local class="uploadingContainer"> <div
transition:padding_scaleY|local
class="uploadingContainer"
>
{#if !upload[1].uploadStatus.fileId} {#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}
{#if upload[1].uploadStatus.fileId} {#if upload[1].uploadStatus.fileId}
<div style:height="10px" transition:padding_scaleY /> <div
style:height="10px"
transition:padding_scaleY
/>
{#if !upload[1].viewingUrl} {#if !upload[1].viewingUrl}
<div class="buttonContainer" out:_void in:_void={{easingFunc:circOut}}> <div
<button on:click={() => uploads[upload[0]].viewingUrl = true}> class="buttonContainer"
out:_void
in:_void={{ easingFunc: circOut }}
>
<button
on:click={() =>
(uploads[
upload[0]
].viewingUrl = true)}
>
view url view url
</button> </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 copy url
</button> </button>
</div> </div>
{:else} {:else}
<div class="buttonContainer" out:_void in:_void={{easingFunc:circOut}}> <div
<input type="text" readonly value={`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`} style:flex-basis="80%"> class="buttonContainer"
<button on:click={() => uploads[upload[0]].viewingUrl = false} style:flex-basis="20%"> 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 ok
</button> </button>
</div> </div>
@ -197,46 +345,71 @@
</div> </div>
{#if uploadInProgress == false} {#if uploadInProgress == false}
<!-- if required for upload, check if logged in --> <!-- if required for upload, check if logged in -->
{#if ($serverStats.accounts || {}).requiredForUpload ? !!$account.username : true} {#if ($serverStats.accounts || {}).requiredForUpload ? !!$account.username : true}
<AttachmentZone
<AttachmentZone bind:this={attachmentZone} on:addFiles={handle_file_upload}/> bind:this={attachmentZone}
<div style:min-height="10px" transition:_void={{rTarg:"height",prop:"min-height"}} /> on:addFiles={handle_file_upload}
/>
<div
style:min-height="10px"
transition:_void={{ rTarg: "height", prop: "min-height" }}
/>
{#if Object.keys(uploads).length > 0} {#if Object.keys(uploads).length > 0}
<button in:padding_scaleY={{easingFunc:circOut}} out:_void on:click={upload_files}>upload</button> <button
<div transition:_void={{rTarg:"height",prop:"min-height"}} style:min-height="10px" /> 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} {/if}
{:else} {:else}
<p transition:_void style:color="#999999" style:text-align="center">
<p transition:_void style:color="#999999" style:text-align="center">Please log in to upload files.</p> Please log in to upload files.
<div transition:_void={{rTarg:"height",prop:"min-height"}} style:min-height="10px" /> </p>
<div
transition:_void={{ rTarg: "height", prop: "min-height" }}
style:min-height="10px"
/>
{/if} {/if}
{/if} {/if}
<p style:color="#999999" style:text-align="center"> <p style:color="#999999" style:text-align="center">
Hosting <span class="number" style:font-weight="600">{$serverStats.files || "•••"}</span> files Hosting <span class="number" style:font-weight="600"
>{$serverStats.files || "•••"}</span
Maximum filesize is <span class="number" style:font-weight="600">{(($serverStats.maxDiscordFileSize || 0)*($serverStats.maxDiscordFiles || 0))/1048576 || "•••"}MiB</span> >
files — Maximum filesize is
<span class="number" style:font-weight="600"
>{(($serverStats.maxDiscordFileSize || 0) *
($serverStats.maxDiscordFiles || 0)) /
1048576 || "•••"}MiB</span
>
<br /> <br />
</p> </p>
<p style:color="#999999" style:text-align="center" style:font-size="12px"> <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 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" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 173.8 275.72" viewBox="0 0 173.8 275.72"
height="16" height="16"
style="vertical-align:middle" style="vertical-align:middle"
fill="currentColor"> fill="currentColor"
>
<circle cx="37.13" cy="26.43" r="21.6" /> <circle cx="37.13" cy="26.43" r="21.6" />
<circle cx="34.62" cy="117.87" r="34.62" class="middle" /> <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="119.78" cy="130.68" r="21.6" class="middle" />
<circle cx="127.16" cy="46.64" r="46.65" /> <circle cx="127.16" cy="46.64" r="46.65" />
<circle cx="102.68" cy="219.58" r="56.14" class="bottom" /> <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> </svg> Etcetera</a
>
<a href="https://github.com/mollersuite/monofile" style:font-size="12px"
>source</a
>
</p> </p>
<div style:height="10px" /> <div style:height="10px" />
</div> </div>