Merge pull request #34 from mollersuite/notifs

This commit is contained in:
Jack W 2023-10-14 16:42:25 -04:00 committed by GitHub
commit cbb9152529
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 296 additions and 147 deletions

4
.vscode/tasks.json vendored
View file

@ -3,7 +3,7 @@
"tasks": [ "tasks": [
{ {
"type": "shell", "type": "shell",
"command":"tsc\nsass src/style:out/style\nrollup -c", "command":"npm run build",
"group": { "group": {
"kind": "build", "kind": "build",
"isDefault": true "isDefault": true
@ -12,7 +12,7 @@
}, },
{ {
"type": "shell", "type": "shell",
"command":"tsc\nsass src/style:out/style\nrollup -c\nnode ./out/server/index.js\ndel ./out/* -Recurse", "command":"npm run build\nnode ./out/server/index.js\ndel ./out/* -Recurse",
"group": { "group": {
"kind": "build", "kind": "build",
"isDefault": true "isDefault": true

39
package-lock.json generated
View file

@ -1,17 +1,14 @@
{ {
"name": "monofile", "name": "monofile",
"version": "1.3.0-beta", "version": "1.4.0-dev",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "monofile", "name": "monofile",
"version": "1.3.0-beta", "version": "1.4.0-dev",
"license": "Unlicense", "license": "Unlicense",
"dependencies": { "dependencies": {
"@fontsource/fira-code": "^5.0.8",
"@fontsource/inconsolata": "^5.0.8",
"@fontsource/source-sans-pro": "^5.0.8",
"@types/body-parser": "^1.19.2", "@types/body-parser": "^1.19.2",
"@types/express": "^4.17.14", "@types/express": "^4.17.14",
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
@ -37,7 +34,7 @@
"svelte": "^3.55.1" "svelte": "^3.55.1"
}, },
"engines": { "engines": {
"node": ">=v18" "node": ">=v16.11"
} }
}, },
"node_modules/@discordjs/builders": { "node_modules/@discordjs/builders": {
@ -90,21 +87,6 @@
"node": ">=16.9.0" "node": ">=16.9.0"
} }
}, },
"node_modules/@fontsource/fira-code": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-5.0.8.tgz",
"integrity": "sha512-kp/tJUVnjaZeLHENMBFTTSgP2B7+/rIboeofuMfoGB40s2U0DKXNqQcOqIF5PtDhJ5QTG1LcviYXMnc1yG6oYQ=="
},
"node_modules/@fontsource/inconsolata": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/inconsolata/-/inconsolata-5.0.8.tgz",
"integrity": "sha512-KpBU6q1yCovfycaFprVEauh8U5RsWty3konFfUukyRRxZBK4Sf73XmGQc8iJ4CPrOP4dplGfdX2kjbRgdymajA=="
},
"node_modules/@fontsource/source-sans-pro": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/source-sans-pro/-/source-sans-pro-5.0.8.tgz",
"integrity": "sha512-5U2UvIYRkCMozZ388gCE73PEpa2MFgN/0t9O4a1FF7bGT/MIneQWSL1XpWZ8iMVYdh6ntxRf3iFA6slCIuFgkg=="
},
"node_modules/@rollup/plugin-node-resolve": { "node_modules/@rollup/plugin-node-resolve": {
"version": "15.0.1", "version": "15.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz",
@ -1750,21 +1732,6 @@
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-0.1.0.tgz", "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-0.1.0.tgz",
"integrity": "sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ==" "integrity": "sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ=="
}, },
"@fontsource/fira-code": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-5.0.8.tgz",
"integrity": "sha512-kp/tJUVnjaZeLHENMBFTTSgP2B7+/rIboeofuMfoGB40s2U0DKXNqQcOqIF5PtDhJ5QTG1LcviYXMnc1yG6oYQ=="
},
"@fontsource/inconsolata": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/inconsolata/-/inconsolata-5.0.8.tgz",
"integrity": "sha512-KpBU6q1yCovfycaFprVEauh8U5RsWty3konFfUukyRRxZBK4Sf73XmGQc8iJ4CPrOP4dplGfdX2kjbRgdymajA=="
},
"@fontsource/source-sans-pro": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@fontsource/source-sans-pro/-/source-sans-pro-5.0.8.tgz",
"integrity": "sha512-5U2UvIYRkCMozZ388gCE73PEpa2MFgN/0t9O4a1FF7bGT/MIneQWSL1XpWZ8iMVYdh6ntxRf3iFA6slCIuFgkg=="
},
"@rollup/plugin-node-resolve": { "@rollup/plugin-node-resolve": {
"version": "15.0.1", "version": "15.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz",

View file

@ -5,6 +5,7 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "node ./out/server/index.js", "start": "node ./out/server/index.js",
"build": "tsc\nsass src/style:out/style\nrollup -c",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"keywords": [], "keywords": [],

View file

@ -23,6 +23,20 @@
font-size: 14px; font-size: 14px;
} }
h1 > button {
background-color: #0000 !important;
padding: 0;
margin-top: 5px;
img {
color: #666666;
}
&:hover {
img { color: #DDD; }
}
}
a { a {
color:#999; color:#999;
} }

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,31 +25,31 @@
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
} else if (ev.detail.type == "upload") { } else if (ev.detail.type == "upload") {
ev.detail.files.forEach((v,x) => { ev.detail.files.forEach((v, x) => {
uploads[Math.random().toString().slice(2)] = { uploads[Math.random().toString().slice(2)] = {
type: "upload", type: "upload",
name: v.name, name: v.name,
file: v, file: v,
params: { params: {
uploadId: "" uploadId: "",
}, },
uploadStatus:{ uploadStatus: {
fileId: null, fileId: null,
error: null, error: null,
} },
} }
}) })
@ -57,17 +57,47 @@
} }
} }
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: "copy",
title: "Copy",
},
],
}).addEventListener(
"notificationclick",
({ action }) => {
if (action === "open") {
open(
"/download/" +
uploads[x].uploadStatus.fileId
)
} 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 +107,43 @@
let sequential = localStorage.getItem("sequentialMode") == "true" let sequential = localStorage.getItem("sequentialMode") == "true"
// go through all files // 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 // 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 // switch will have a proper menu option later, for now i'm lazy so it's just gonna be a Secret
let hdl = () => { let hdl = () => {
switch(v.type) { switch (v.type) {
case "upload": case "upload":
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 +152,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="20"
height="20"
fill="currentColor"
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="currentColor"
/></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 +206,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 +339,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>