mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-22 16:55:13 +02:00
Refactoring the resource details page
This commit is contained in:
@@ -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}
|
||||
|
||||
79
src/lib/components/RequestModal/SeriesRequestModal.svelte
Normal file
79
src/lib/components/RequestModal/SeriesRequestModal.svelte
Normal 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>
|
||||
Reference in New Issue
Block a user