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>
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
@ -13,10 +13,10 @@
// 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,17 +57,53 @@
}
}
let handle_fetch_promise = (x,prom) => {
return prom.then(async (res) => {
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;
if (txt.startsWith("[err]")) uploads[x].uploadStatus.error = txt
else {
uploads[x].uploadStatus.fileId = txt;
refresh_stats();
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((err) => {
uploads[x].uploadStatus.error = err.toString();
}
)
} catch (_) {}
refresh_stats()
}
})
.catch((err) => {
uploads[x].uploadStatus.error = err.toString()
})
}
@ -77,37 +113,43 @@
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",{
return handle_fetch_promise(
x,
fetch("/upload", {
headers: {
"monofile-params": JSON.stringify(v.params)
"monofile-params": JSON.stringify(v.params),
},
method: "POST",
body: fd
}))
body: fd,
})
)
break
case "clone":
return handle_fetch_promise(x,fetch("/clone",{
return handle_fetch_promise(
x,
fetch("/clone", {
method: "POST",
body: JSON.stringify({
url: v.url,
...v.params
...v.params,
}),
})
}))
)
break
}
}
if (sequential) await hdl();
else hdl();
if (sequential) await hdl()
else hdl()
}
}
@ -116,23 +158,50 @@
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>&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>
<div style:min-height="10px" />
@ -143,46 +212,125 @@
{#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" ? `&nbsp;(${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"
? `&nbsp;(${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>
@ -197,46 +345,71 @@
</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
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">
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>
</svg> Etcetera</a
>
<a href="https://github.com/mollersuite/monofile" style:font-size="12px"
>source</a
>
</p>
<div style:height="10px" />
</div>