Refactoring the resource details page

This commit is contained in:
Aleksi Lassila
2023-07-23 14:41:10 +03:00
parent 7b6da297c3
commit 51a7ab630a
25 changed files with 1097 additions and 586 deletions

View File

@@ -1,59 +1,96 @@
<script lang="ts">
import Modal from '../Modal/Modal.svelte';
import ModalContent from '../Modal/ModalContent.svelte';
import { fetchRadarrReleases } from '$lib/apis/radarr/radarrApi';
import { fetchSonarrReleases } from '$lib/apis/sonarr/sonarrApi';
import { formatMinutesToTime, formatSize } from '$lib/utils';
import IconButton from '../IconButton.svelte';
import { DotFilled, Download, Plus } from 'radix-icons-svelte';
import { createEventDispatcher } from 'svelte';
import ModalHeader from '../Modal/ModalHeader.svelte';
import { log } from '$lib/utils.js';
import HeightHider from '../HeightHider.svelte';
import IconButton from '../IconButton.svelte';
import Modal from '../Modal/Modal.svelte';
import ModalContent from '../Modal/ModalContent.svelte';
import ModalHeader from '../Modal/ModalHeader.svelte';
const dispatch = createEventDispatcher();
// TODO: Switch to grid
export let visible = true; // FIXME
function close() {
visible = false;
downloadFetching = false;
downloadingGuid = null;
export let radarrId: number | undefined = undefined;
export let sonarrEpisodeId: number | undefined = undefined;
let showAllReleases = false;
let showDetailsId: string | null = null;
let downloadFetchingGuid: string | undefined;
let downloadingGuid: string | undefined;
let releasesResponse: ReturnType<typeof fetchReleases>;
$: if (visible && !releasesResponse) {
releasesResponse = fetchReleases();
}
export let radarrId;
async function fetchReleases() {
if (!radarrId && !sonarrEpisodeId) {
return {
releases: [],
filtered: [],
releasesSkipped: 0
};
}
let releasesResponse;
$: if (visible) {
releasesResponse = fetch(`/movie/${radarrId}/releases`).then((res) => log(res.json()));
const releases = radarrId
? await fetchRadarrReleases(radarrId)
: await fetchSonarrReleases(sonarrEpisodeId as number);
let filtered = releases.slice();
filtered.sort((a, b) => (b.seeders || 0) - (a.seeders || 0));
filtered = (filtered as any)
.filter((release: any) => release?.quality?.quality?.resolution > 720)
.slice(0, 5);
const releasesSkipped = releases.length - filtered.length;
releases.sort((a, b) => (b.size || 0) - (a.size || 0));
filtered.sort((a, b) => (b.size || 0) - (a.size || 0));
return {
releases,
filtered,
releasesSkipped
};
}
let downloadFetching;
let downloadingGuid;
function handleDownload(guid) {
downloadFetching = guid;
function handleDownload(guid: string) {
downloadFetchingGuid = guid;
fetch('/movie/0/releases', {
method: 'POST',
body: JSON.stringify({ guid })
}).then((res) => {
dispatch('download');
downloadFetching = false;
downloadFetchingGuid = undefined;
if (res.ok) {
downloadingGuid = guid;
}
});
}
let showAllReleases = false;
function toggleShowAll() {
showAllReleases = !showAllReleases;
}
let showDetailsId;
function toggleShowDetails(id) {
function toggleShowDetails(id: string | null) {
if (showDetailsId === id) {
showDetailsId = null;
} else {
showDetailsId = id;
}
}
function close() {
visible = false;
downloadFetchingGuid = undefined;
downloadingGuid = undefined;
}
</script>
<Modal {visible} {close}>
@@ -61,32 +98,34 @@
<ModalHeader {close} text="Releases" />
{#await releasesResponse}
<div class="text-sm text-zinc-200 opacity-50 font-light p-4">Loading...</div>
{:then data}
{#if showAllReleases ? data?.allReleases?.length : data?.filtered?.length}
{:then { releases, filtered, releasesSkipped }}
{#if showAllReleases ? releases?.length : filtered?.length}
<div class="flex flex-col py-2 divide-y divide-zinc-700 max-h-[60vh] overflow-y-scroll">
{#each showAllReleases ? data.allReleases : data.filtered as release}
{#each showAllReleases ? releases : filtered as release}
<div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="flex px-4 py-2 gap-4 hover:bg-lighten items-center justify-between cursor-pointer text-sm"
on:click={() => toggleShowDetails(release.guid)}
on:click={() => toggleShowDetails(release.guid || null)}
>
<div class="flex gap-4">
<div class="tracking-wide font-medium">{release.indexer}</div>
<div class="text-zinc-400">{release.quality.quality.name}</div>
<div class="text-zinc-400">{release?.quality?.quality?.name}</div>
<div class="text-zinc-400">{release.seeders} seeders</div>
</div>
<div class="flex gap-2 items-center">
<div class="text-zinc-400">{formatSize(release.size)}</div>
<div class="text-zinc-400">{formatSize(release?.size || 0)}</div>
{#if release.guid !== downloadingGuid}
<IconButton
on:click={() => handleDownload(release.guid)}
disabled={downloadFetching === release.guid}
on:click={() => release.guid && handleDownload(release.guid)}
disabled={downloadFetchingGuid === release.guid}
>
<Plus size="20" />
<Plus size={20} />
</IconButton>
{:else}
<div class="p-1">
<Download size="20" />
<Download size={20} />
</div>
{/if}
</div>
@@ -96,14 +135,14 @@
<div>
{release.title}
</div>
<DotFilled size="15" />
<div>{formatMinutesToTime(release.ageMinutes)} old</div>
<DotFilled size="15" />
<DotFilled size={15} />
<div>{formatMinutesToTime(release.ageMinutes || 0)} old</div>
<DotFilled size={15} />
<div><b>{release.seeders} seeders</b> / {release.leechers} leechers</div>
<DotFilled size="15" />
<DotFilled size={15} />
{#if release.seeders}
<div>
{formatSize(release.size / release.seeders)} per seeder
{formatSize((release.size || 0) / release.seeders)} per seeder
</div>
{/if}
</div>
@@ -111,12 +150,12 @@
</div>
{/each}
</div>
{#if data?.releasesSkipped > 0}
{#if releasesSkipped > 0}
<div
class="text-sm text-zinc-200 opacity-50 font-light px-4 py-2 hover:underline cursor-pointer"
on:click={toggleShowAll}
>
{showAllReleases ? 'Show less' : `Show all ${data.releasesSkipped} releases`}
{showAllReleases ? 'Show less' : `Show all ${releasesSkipped} releases`}
</div>
{/if}
{:else}

View File

@@ -0,0 +1,79 @@
<script lang="ts">
import { fetchSonarrEpisodes, type SonarrEpisode } from '$lib/apis/sonarr/sonarrApi';
import Modal from '../Modal/Modal.svelte';
import ModalContent from '../Modal/ModalContent.svelte';
import ModalHeader from '../Modal/ModalHeader.svelte';
export let visible = false;
export let sonarrId: number;
let episodesPromise: ReturnType<typeof fetchEpisodes>;
$: if (visible && !episodesPromise) {
episodesPromise = fetchEpisodes(sonarrId);
}
function close() {
visible = false;
}
async function fetchEpisodes(sonarrId: number) {
return fetchSonarrEpisodes(sonarrId).then((episodes) => {
// Group episodes by season
const seasons: SonarrEpisode[][] = [];
episodes.forEach((episode) => {
if (!episode.seasonNumber) {
return;
}
if (!seasons[episode.seasonNumber - 1]) {
seasons[episode.seasonNumber - 1] = [];
}
seasons[episode.seasonNumber - 1].push(episode);
});
return seasons;
});
}
</script>
<Modal {visible} {close}>
<ModalContent>
<ModalHeader {close} text="Seasons" />
<div class="flex flex-col gap-2 sm:gap-4">
{#await episodesPromise then seasons}
{console.log('saesons', seasons)}
{#each seasons as episodes, i}
{#if i > 0}
<div class="border-t border-gray-200" />
{/if}
{#each episodes as episode}
<div class="flex flex-row items-center justify-between">
<div class="flex flex-row items-center gap-2">
<div class="flex flex-col">
<div class="text-sm font-medium text-gray-900">
{episode.episodeNumber}. {episode.title}
</div>
<div class="text-sm text-gray-500">
{episode.airDate}
</div>
</div>
</div>
<div class="flex flex-row items-center gap-2">
<div class="flex flex-col">
<div class="text-sm font-medium text-gray-900">
{episode.episodeNumber}. {episode.title}
</div>
<div class="text-sm text-gray-500">
{episode.airDate}
</div>
</div>
</div>
</div>
{/each}
{/each}
{/await}
</div>
</ModalContent>
</Modal>