Compare commits

...

2 Commits

Author SHA1 Message Date
Jill ae4f4c6498 toasts, autodownload 2022-08-19 00:41:49 +03:00
Jill 37dc206ccf mobile design & fixes 2022-08-19 00:01:08 +03:00
10 changed files with 175 additions and 64 deletions

View File

@ -4,9 +4,11 @@
import Loading from './lib/Loading.svelte'; import Loading from './lib/Loading.svelte';
import Search from './lib/Search.svelte'; import Search from './lib/Search.svelte';
import ThemeSwitcher from './lib/ThemeSwitcher.svelte'; import ThemeSwitcher from './lib/ThemeSwitcher.svelte';
import { queue } from './lib/stores'; import { queue, saveOnDownload } from './lib/stores';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import ProgressBar from './lib/ProgressBar.svelte'; import { dev } from './lib/dev';
import ProgressBar from './lib/ProgressBar.svelte';
import { SvelteToast } from '@zerodevx/svelte-toast'
let loading = false; let loading = false;
@ -17,7 +19,7 @@ import ProgressBar from './lib/ProgressBar.svelte';
loading = true; loading = true;
try { try {
let url = new URL('/api/search', window.location.origin); let url = dev ? (new URL('http://localhost:4500/api/search')) : (new URL('/api/search', window.location.origin));
url.searchParams.set('search', query); url.searchParams.set('search', query);
const response = await fetch(url); const response = await fetch(url);
const data = await response.json(); const data = await response.json();
@ -32,12 +34,17 @@ import ProgressBar from './lib/ProgressBar.svelte';
let searchAlbums = []; let searchAlbums = [];
</script> </script>
<SvelteToast options={{
theme: {
'--toastBorderRadius': '0.75em',
'--toastBackground': 'rgba(19, 19, 19, 0.7)'
}
}}/>
<app> <app>
<main> <main>
<span class="main"> <span class="main">
<Header/> <Header/>
<Search onChange={search}/> <Search onChange={search}/>
<i class="small">ps. sorry for shitty mobile support on rewrite i'll fix it soon i promiseeeee -oat</i>
{#if loading} {#if loading}
<Loading/> <Loading/>
{/if} {/if}
@ -60,6 +67,9 @@ import ProgressBar from './lib/ProgressBar.svelte';
</div> </div>
{/each} {/each}
</div> </div>
<form class="options">
<input type="checkbox" id="auto-save" bind:checked={$saveOnDownload}/><label for="auto-save">Save songs on download <span class="small">(Doesn't work on all browsers)</span></label>
</form>
</sidebar> </sidebar>
</app> </app>
@ -76,30 +86,59 @@ import ProgressBar from './lib/ProgressBar.svelte';
} }
main { main {
flex: 1 1 0px; flex: 1 1 0px;
min-height: 100vh;
} }
sidebar { sidebar {
width: 0px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
padding: 0em;
height: calc(100vh - 2em);
overflow: auto;
overflow-x: hidden;
position: sticky;
top: 0px;
right: 0px;
transition: width 0.2s ease-in-out, padding 0.2s ease-in-out, right 0.2s ease-in-out;
}
sidebar.open {
width: 450px;
padding: 1em; padding: 1em;
right: 8px; display: flex;
flex-direction: column;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
transition: width 0.2s ease-in-out, padding 0.2s ease-in-out, right 0.2s ease-in-out, height 0.2s ease-in-out;
}
@media (min-width: 1100px) {
sidebar {
height: calc(100vh - 2em);
overflow: auto;
overflow-x: hidden;
position: sticky;
top: 0px;
width: 0px;
right: 0px;
}
sidebar.open {
width: 450px;
right: 8px;
}
sidebar.open {
padding: 1em;
}
}
@media (max-width: 1099px) {
app {
flex-direction: column;
}
sidebar {
order: -1;
align-items: center;
}
sidebar:not(.open) {
height: 0px;
padding: 0em;
overflow: hidden;
}
} }
.queue { .queue {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1em; gap: 1em;
max-width: 380px;
}
.options {
padding-top: 1em;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {

View File

@ -48,6 +48,7 @@ body {
.small { .small {
font-size: medium; font-size: medium;
font-weight: normal;
} }
.big { .big {
font-weight: bold; font-weight: bold;

View File

@ -18,6 +18,7 @@
import Track from './Track.svelte'; import Track from './Track.svelte';
import { startDownload } from './download'; import { startDownload } from './download';
import { dev } from './dev';
const options = {}; const options = {};
@ -27,7 +28,7 @@
if (loadingTracks || album || short) return; if (loadingTracks || album || short) return;
loadingTracks = true; loadingTracks = true;
try { try {
let url = new URL(`/api/album`, window.location.origin); let url = dev ? (new URL('http://localhost:4500/api/album')) : (new URL('/api/album', window.location.origin));
url.searchParams.set('id', id); url.searchParams.set('id', id);
const response = await fetch(url); const response = await fetch(url);
album = await response.json(); album = await response.json();
@ -43,15 +44,36 @@
use:inview={options} use:inview={options}
on:enter={loadTracks} on:enter={loadTracks}
> >
<div class="album-metadata"> <div class="album-inner-top">
<span class="metadata"> <div class="album-metadata">
<span class="big">{title}</span> <span class="metadata">
{#if subtitle} <div class="big">
<span class="small">{subtitle}</span> {title}
{#if subtitle}
<span class="small">{subtitle}</span>
{/if}
</div>
<div class="small">{artist.name}</div>
</span>
{#if !hideDownload || $butShowThisDownloadLinkInstead}
{#if $butShowThisDownloadLinkInstead}
<a href={$butShowThisDownloadLinkInstead} target="_blank" rel="noopener" download="{$butShowThisDownloadLinkInstead.split('/').slice(-1)}">
<div class="album-download" title="Download">
<Icon icon={faDownload}/>
</div>
</a>
{:else}
<div class="album-download" title="Download" on:click={() => startDownload(id, {title, artist, cover}, true)}>
<Icon icon={faDownload}/>
</div>
{/if}
{/if} {/if}
<br> </div>
<span class="small">{artist.name}</span> <div class="album-image-wrapper">
</span> <img class="album-image" width="128" height="128" src="https://e-cdns-images.dzcdn.net/images/cover/{cover}/128x128-000000-80-0-0.jpg" alt="Cover for '{title}'">
</div>
</div>
<div class="album-inner-bottom">
{#if log} {#if log}
<div class="progress-state"> <div class="progress-state">
{#each $log as line, i} {#each $log as line, i}
@ -59,22 +81,6 @@
{/each} {/each}
</div> </div>
{/if} {/if}
{#if !hideDownload || $butShowThisDownloadLinkInstead}
{#if $butShowThisDownloadLinkInstead}
<a href={$butShowThisDownloadLinkInstead} target="_blank" rel="noopener" download="{$butShowThisDownloadLinkInstead.split('/').slice(-1)}">
<div class="album-download" title="Download">
<Icon icon={faDownload}/>
</div>
</a>
{:else}
<div class="album-download" title="Download" on:click={() => startDownload(id, {title, artist, cover}, true)}>
<Icon icon={faDownload}/>
</div>
{/if}
{/if}
</div>
<div class="album-image-wrapper">
<img class="album-image" width="128" height="128" src="https://e-cdns-images.dzcdn.net/images/cover/{cover}/128x128-000000-80-0-0.jpg" alt="Cover for '{title}'">
</div> </div>
</div> </div>
@ -84,7 +90,7 @@
{/if} {/if}
{#if album && !short} {#if album && !short}
{#each album.tracks as track} {#each album.tracks as track}
<Track id={track.id} title={track.title} duration={track.duration} artist={track.artist} cover={cover} album={title}/> <Track id={track.id} title={track.title} duration={track.duration} artist={track.artist} cover={cover} album={title} albumArtist={artist.name}/>
{/each} {/each}
{/if} {/if}
</div> </div>
@ -97,9 +103,22 @@
border-radius: 10px 10px 0px 0px; border-radius: 10px 10px 0px 0px;
transition: 0.1s border-left ease-out, 0.1s background-color ease-in-out; transition: 0.1s border-left ease-out, 0.1s background-color ease-in-out;
min-height: 96px; min-height: 96px;
margin-top: 0.5em;
min-width: 330px;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 0.5em;
}
.album-inner-top {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-top: 0.5em; gap: 0.5em;
}
.album-inner-bottom {
display: flex;
flex-direction: column;
align-items: stretch;
} }
.album.short { .album.short {
border-radius: 10px 10px 10px 10px; border-radius: 10px 10px 10px 10px;
@ -113,6 +132,7 @@
height: 96px; height: 96px;
} }
.album-image-wrapper { .album-image-wrapper {
flex: 0 0 auto;
transition: 0.1s border ease-out; transition: 0.1s border ease-out;
} }
.album-metadata { .album-metadata {
@ -121,6 +141,9 @@
flex: 1 1 0px; flex: 1 1 0px;
align-items: flex-start; align-items: flex-start;
gap: 0.5em; gap: 0.5em;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
} }
.album-download { .album-download {
cursor: pointer; cursor: pointer;
@ -141,13 +164,15 @@
.metadata { .metadata {
flex: 1 1 0px; flex: 1 1 0px;
display: flex;
flex-direction: column;
align-items: stretch;
} }
.progress-state { .progress-state {
font-family: monospace; font-family: monospace;
font-size: 12px; font-size: 12px;
border-radius: 10px; border-radius: 10px;
width: 80%;
padding: 6px; padding: 6px;
height: 5.5em; height: 5.5em;
overflow: hidden; overflow: hidden;

View File

@ -50,7 +50,7 @@
input { input {
margin: 5px; margin: 5px;
width: 550px; width: 550px;
max-width: 98%; max-width: 94%;
padding: 15px; padding: 15px;
font-size: x-large; font-size: x-large;
border: none; border: none;

View File

@ -5,6 +5,7 @@
export let cover; export let cover;
export let duration; export let duration;
export let album; export let album;
export let albumArtist;
import { formatTime } from './format'; import { formatTime } from './format';
@ -15,7 +16,13 @@
</script> </script>
<div class="track" id="track-{id}"> <div class="track" id="track-{id}">
<span class="track-left">{artist} - {title}</span> <span class="track-left">
{#if artist !== albumArtist}
{artist} - {title}
{:else}
{title}
{/if}
</span>
<span class="track-right"> <span class="track-right">
<span class="small">{formatTime(duration)}</span> <span class="small">{formatTime(duration)}</span>
<span class="track-download" title="Download" on:click={() => startDownload(id, {title, artist: {name: artist}, cover, album}, false)}> <span class="track-download" title="Download" on:click={() => startDownload(id, {title, artist: {name: artist}, cover, album}, false)}>
@ -44,6 +51,9 @@
.track-left { .track-left {
flex: 1 1 0px; flex: 1 1 0px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
} }
.track-right { .track-right {
flex: 0 0 auto; flex: 0 0 auto;

1
app/src/lib/dev.js Normal file
View File

@ -0,0 +1 @@
export const dev = false;

View File

@ -1,7 +1,14 @@
import { get, writable } from "svelte/store"; import { get, writable } from 'svelte/store';
import { queue } from "./stores"; import { queue, saveOnDownload } from './stores';
import { dev } from './dev';
import { toast } from '@zerodevx/svelte-toast'
import { saveAs } from 'file-saver';
const successTheme = {'--toastBarBackground': 'rgb(131, 243, 131)'};
const failureTheme = {'--toastBarBackground': 'rgb(243, 131, 131)'};
function getWebsocketLocation() { function getWebsocketLocation() {
if (dev) return 'ws://localhost:4500/';
return window.window.location.toString().replace('https://', 'wss://').replace('http://', 'ws://'); return window.window.location.toString().replace('https://', 'wss://').replace('http://', 'ws://');
} }
@ -12,10 +19,6 @@ export function startDownload(id, metadata, isAlbum) {
let success = writable(null); let success = writable(null);
let downloadLink = writable(null); let downloadLink = writable(null);
let coverArt;
let title;
let artist;
let queueItem = { let queueItem = {
id: id, id: id,
...metadata, ...metadata,
@ -26,26 +29,41 @@ export function startDownload(id, metadata, isAlbum) {
downloadLink downloadLink
}; };
toast.push(`Started download for <b>${metadata.artist.name} - ${metadata.title}</b>`);
queue.set([...get(queue), queueItem]); queue.set([...get(queue), queueItem]);
let type = isAlbum ? 'album' : 'track' let type = isAlbum ? 'album' : 'track'
const ws = new WebSocket(`${getWebsocketLocation()}api/${type}?id=${id}`); const ws = new WebSocket(`${getWebsocketLocation()}api/${type}?id=${id}`);
ws.onmessage = (m) => { ws.onmessage = (m) => {
const d = JSON.parse(m.data); const d = JSON.parse(m.data);
console.log(d); //console.log(d);
if (d.key === 'downloadInfo') { if (d.key === 'downloadInfo') {
logLocal.push(`[${d.data.data.title}] ${d.data.state}`); logLocal.push(`[${d.data.data.title}] ${d.data.state}`);
log.set(logLocal); log.set(logLocal);
} else if (d.key === 'updateQueue') { } else if (d.key === 'updateQueue') {
if (d.data.progress) { if (d.data.progress) {
progress.set(d.data.progress); progress.set(Math.max(d.data.progress, get(progress)));
} }
} else if (d.key === 'download') { } else if (d.key === 'download') {
downloadLink.set(d.data);
} else if (d.key === 'finishDownload') {
logLocal.push('Download finished');
log.set(logLocal);
success.set(true); success.set(true);
progress.set(100);
downloadLink.set(d.data);
toast.push(`Downloaded <b>${metadata.artist.name} - ${metadata.title}</b>!`, {theme: successTheme});
if (get(saveOnDownload)) {
saveAs(d.data, d.data.split('/').pop());
}
} else if (d.key === 'finishDownload') {
setTimeout(() => {
if (!get(success)) {
success.set(false);
toast.push(`Downloading <b>${metadata.artist.name} - ${metadata.title}</b> failed!`, {theme: failureTheme});
logLocal.push('Server didn\'t send a download link back!');
logLocal.push('This may be due to errors during the download or temporary connection issues');
logLocal.push('Try again, and if it still doesn\'t work, annoy oat until it does again');
log.set(logLocal);
}
}, 1000);
} else if (d.key === 'zipping') { } else if (d.key === 'zipping') {
logLocal.push('Creating zip archive'); logLocal.push('Creating zip archive');
log.set(logLocal); log.set(logLocal);
@ -53,7 +71,7 @@ export function startDownload(id, metadata, isAlbum) {
} }
ws.onopen = () => { ws.onopen = () => {
logLocal.push('WebSocket connected!'); logLocal.push('WebSocket connected!');
logLocal.push('Server shooould start downloading the files now'); logLocal.push('Initializing download');
log.set(logLocal); log.set(logLocal);
} }
ws.onerror = (e) => { ws.onerror = (e) => {
@ -61,6 +79,7 @@ export function startDownload(id, metadata, isAlbum) {
logLocal.push(`${e}`); logLocal.push(`${e}`);
log.set(logLocal); log.set(logLocal);
success.set(false); success.set(false);
toast.push(`Downloading <b>${metadata.artist.name} - ${metadata.title}</b> failed!`, {theme: failureTheme});
} }
ws.onclose = (e) => { ws.onclose = (e) => {
if (e.code !== 1000) { if (e.code !== 1000) {
@ -68,6 +87,7 @@ export function startDownload(id, metadata, isAlbum) {
logLocal.push(`websocket closed unexpectedly with code ${e.code}`, `${e.reason}`); logLocal.push(`websocket closed unexpectedly with code ${e.code}`, `${e.reason}`);
log.set(logLocal); log.set(logLocal);
success.set(false); success.set(false);
toast.push(`Downloading <b>${metadata.artist.name} - ${metadata.title}</b> failed!`, {theme: failureTheme});
} }
} }
} }

View File

@ -1,3 +1,4 @@
import { writable } from "svelte/store"; import { writable } from "svelte/store";
export let queue = writable([]); export let queue = writable([]);
export let saveOnDownload = writable(false);

View File

@ -24,11 +24,13 @@
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@fortawesome/free-solid-svg-icons": "^6.1.2", "@fortawesome/free-solid-svg-icons": "^6.1.2",
"@zerodevx/svelte-toast": "^0.7.2",
"deemix": "git+https://gitlab.com/RemixDev/deemix-js", "deemix": "git+https://gitlab.com/RemixDev/deemix-js",
"deezer-js": "^1.3.5", "deezer-js": "^1.3.5",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"express": "^4.17.3", "express": "^4.17.3",
"express-ws": "^5.0.2", "express-ws": "^5.0.2",
"file-saver": "^2.0.5",
"sirv-cli": "^2.0.2", "sirv-cli": "^2.0.2",
"svelte-fontawesome": "^0.0.3", "svelte-fontawesome": "^0.0.3",
"svelte-inview": "^3.0.1", "svelte-inview": "^3.0.1",

View File

@ -7,12 +7,14 @@ specifiers:
'@types/express': ^4.17.13 '@types/express': ^4.17.13
'@types/express-ws': ^3.0.1 '@types/express-ws': ^3.0.1
'@types/ws': ^8.2.3 '@types/ws': ^8.2.3
'@zerodevx/svelte-toast': ^0.7.2
bufferutil: ^4.0.6 bufferutil: ^4.0.6
deemix: git+https://gitlab.com/RemixDev/deemix-js deemix: git+https://gitlab.com/RemixDev/deemix-js
deezer-js: ^1.3.5 deezer-js: ^1.3.5
dotenv: ^10.0.0 dotenv: ^10.0.0
express: ^4.17.3 express: ^4.17.3
express-ws: ^5.0.2 express-ws: ^5.0.2
file-saver: ^2.0.5
rollup: ^2.68.0 rollup: ^2.68.0
rollup-plugin-css-only: ^3.1.0 rollup-plugin-css-only: ^3.1.0
rollup-plugin-livereload: ^2.0.5 rollup-plugin-livereload: ^2.0.5
@ -31,11 +33,13 @@ specifiers:
dependencies: dependencies:
'@fortawesome/free-solid-svg-icons': 6.1.2 '@fortawesome/free-solid-svg-icons': 6.1.2
'@zerodevx/svelte-toast': 0.7.2
deemix: gitlab.com/RemixDev/deemix-js/a105b03beb93cc2efa5d723d7f2e3cb2237d4f08 deemix: gitlab.com/RemixDev/deemix-js/a105b03beb93cc2efa5d723d7f2e3cb2237d4f08
deezer-js: 1.3.5 deezer-js: 1.3.5
dotenv: 10.0.0 dotenv: 10.0.0
express: 4.17.3 express: 4.17.3
express-ws: 5.0.2_te4mbskzuxzlkfrbqjipouglzi express-ws: 5.0.2_te4mbskzuxzlkfrbqjipouglzi
file-saver: 2.0.5
sirv-cli: 2.0.2 sirv-cli: 2.0.2
svelte-fontawesome: 0.0.3 svelte-fontawesome: 0.0.3
svelte-inview: 3.0.1_svelte@3.46.4 svelte-inview: 3.0.1_svelte@3.46.4
@ -284,6 +288,10 @@ packages:
'@types/node': 17.0.21 '@types/node': 17.0.21
dev: true dev: true
/@zerodevx/svelte-toast/0.7.2:
resolution: {integrity: sha512-vWiY6IqsstcOoQ8PFBuFuxgPkj1JFAGhUF9gC7wLx7c5A9SSfdtxWs/39ekGSIeyJK0yqWhTcmzGrCEWSELzDw==}
dev: false
/accepts/1.3.8: /accepts/1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@ -714,6 +722,10 @@ packages:
resolution: {integrity: sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==} resolution: {integrity: sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==}
dev: false dev: false
/file-saver/2.0.5:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
dev: false
/fill-range/7.0.1: /fill-range/7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -1060,7 +1072,7 @@ packages:
dev: false dev: false
/methods/1.1.2: /methods/1.1.2:
resolution: {integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=} resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
dev: false dev: false
@ -1695,7 +1707,7 @@ packages:
node-gyp-build: 4.3.0 node-gyp-build: 4.3.0
/util-deprecate/1.0.2: /util-deprecate/1.0.2:
resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: false dev: false
/utils-merge/1.0.1: /utils-merge/1.0.1: