mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-20 15:55:14 +02:00
refactor: Movie, Series, Episode pages, data fetching
This commit is contained in:
@@ -1,51 +0,0 @@
|
||||
<script lang="ts">
|
||||
import ButtonGhost from '../Ghosts/ButtonGhost.svelte';
|
||||
import Button from '../Button.svelte';
|
||||
import { formatSize } from '../../utils';
|
||||
import { ChevronRight } from 'radix-icons-svelte';
|
||||
import type { Download } from '../../apis/combined-types';
|
||||
import Container from '../Container.svelte';
|
||||
|
||||
export let downloads: Promise<Download[]>;
|
||||
export let cancelDownload: (downloadId: number) => Promise<any>;
|
||||
</script>
|
||||
|
||||
<Container class="flex flex-col -my-2">
|
||||
{#await downloads}
|
||||
{#each new Array(5) as _, index}
|
||||
<div class="flex-1 my-2">
|
||||
<ButtonGhost />
|
||||
</div>
|
||||
{/each}
|
||||
{:then downloads}
|
||||
{#each downloads as download, index}
|
||||
<div class="my-2">
|
||||
<Button on:clickOrSelect={() => cancelDownload(download.id || -1)} let:hasFocus>
|
||||
<div class="flex w-full">
|
||||
<h1 class="flex-1 line-clamp-1">
|
||||
{download.title}
|
||||
</h1>
|
||||
<div>
|
||||
{#if !hasFocus}
|
||||
{#if download.status === 'downloading'}
|
||||
{formatSize((download.size || 0) - (download.sizeleft || 0))}/{formatSize(
|
||||
download.size || 0
|
||||
)}
|
||||
{:else}
|
||||
{download.status}
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex items-center">
|
||||
Cancel
|
||||
<ChevronRight size={19} class="ml-1" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<h1 class="text-sm text-zinc-400">No downloads found</h1>
|
||||
{/each}
|
||||
{/await}
|
||||
</Container>
|
||||
@@ -1,12 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Container from '../Container.svelte';
|
||||
|
||||
export let focusOnMount = false;
|
||||
</script>
|
||||
|
||||
<Container class="flex flex-col my-16" {focusOnMount}>
|
||||
<h1 class="tracking-wide text-2xl font-semibold mb-4">
|
||||
<slot name="header">Header is missing</slot>
|
||||
</h1>
|
||||
<slot>Content is missing</slot>
|
||||
</Container>
|
||||
@@ -1,30 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Container from '../../Container.svelte';
|
||||
import { formatSize } from '../../../utils';
|
||||
import Button from '../../Button.svelte';
|
||||
import FullScreenModal from '../../Modal/FullScreenModal.svelte';
|
||||
import FullScreenModalContainer from '../MediaManagerMenuLayout.svelte';
|
||||
import type { FileResource } from '../../../apis/combined-types';
|
||||
|
||||
export let modalId: symbol;
|
||||
export let file: FileResource;
|
||||
export let handleDeleteFile: (fileId: number) => Promise<any>;
|
||||
</script>
|
||||
|
||||
<FullScreenModal {modalId}>
|
||||
<FullScreenModalContainer>
|
||||
<div slot="header" class="flex">
|
||||
<h1 class="line-clamp-1 flex-1 mr-4">
|
||||
{file.relativePath}
|
||||
</h1>
|
||||
<h1 class="text-zinc-300">{formatSize(file.size || 0)}</h1>
|
||||
</div>
|
||||
<Container>
|
||||
<div class="-my-1">
|
||||
<Button focusOnMount on:clickOrSelect={() => file.id && handleDeleteFile(file.id)}>
|
||||
Delete File
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</FullScreenModalContainer>
|
||||
</FullScreenModal>
|
||||
@@ -1,56 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Container from '../../Container.svelte';
|
||||
import { formatSize } from '../../../utils';
|
||||
import Button from '../../Button.svelte';
|
||||
import FullScreenModal from '../../Modal/FullScreenModal.svelte';
|
||||
import FullScreenModalContainer from '../MediaManagerMenuLayout.svelte';
|
||||
import { useActionRequest } from '../../../stores/data.store';
|
||||
import { Download, Plus } from 'radix-icons-svelte';
|
||||
import type { Release } from '../../../apis/combined-types';
|
||||
|
||||
export let modalId: symbol;
|
||||
export let release: Release;
|
||||
export let status: undefined | 'downloading' | 'downloaded' = undefined;
|
||||
export let grabRelease: (guid: string, indexerId: number) => Promise<boolean>;
|
||||
|
||||
const {
|
||||
send: handleGrabRelease,
|
||||
isFetching,
|
||||
data
|
||||
} = useActionRequest((guid: string, indexerId: number) => grabRelease(guid, indexerId));
|
||||
</script>
|
||||
|
||||
<FullScreenModal {modalId}>
|
||||
<FullScreenModalContainer>
|
||||
<div slot="header" class="flex">
|
||||
<h1 class="line-clamp-1 flex-1 mr-4">
|
||||
{release.title}
|
||||
</h1>
|
||||
<h1 class="text-zinc-300">{formatSize(release.size || 0)}</h1>
|
||||
</div>
|
||||
<Container>
|
||||
<div class="-my-1">
|
||||
<Button
|
||||
focusOnMount
|
||||
on:clickOrSelect={() => handleGrabRelease(release.guid || '', release.indexerId || -1)}
|
||||
disabled={!!($data || $isFetching || status)}
|
||||
>
|
||||
{#if $data || status === 'downloading'}
|
||||
Downloading...
|
||||
{:else if status === 'downloaded'}
|
||||
Downloaded
|
||||
{:else}
|
||||
Download
|
||||
{/if}
|
||||
|
||||
<svelte:component
|
||||
this={$data || status ? Download : Plus}
|
||||
size={19}
|
||||
slot="icon"
|
||||
class="mr-2"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</FullScreenModalContainer>
|
||||
</FullScreenModal>
|
||||
@@ -1,14 +0,0 @@
|
||||
<script lang="ts">
|
||||
import FullScreenModal from '../../Modal/FullScreenModal.svelte';
|
||||
import ReleaseList from '../../MediaManagerModal/Releases/MMReleasesTab.svelte';
|
||||
import type { Release } from '../../../apis/combined-types';
|
||||
|
||||
export let modalId: symbol;
|
||||
export let hidden: boolean;
|
||||
export let getReleases: () => Promise<Release[]>;
|
||||
export let selectRelease: (release: Release) => void;
|
||||
</script>
|
||||
|
||||
<FullScreenModal {modalId} {hidden}>
|
||||
<ReleaseList {getReleases} {selectRelease} />
|
||||
</FullScreenModal>
|
||||
@@ -1,96 +0,0 @@
|
||||
<script lang="ts">
|
||||
import FullScreenModal from '../../Modal/FullScreenModal.svelte';
|
||||
import ManageMediaMenuLayout from '../MediaManagerMenuLayout.svelte';
|
||||
import {
|
||||
type MovieFileResource,
|
||||
radarrApi,
|
||||
type RadarrRelease
|
||||
} from '../../../apis/radarr/radarr-api';
|
||||
import ReleaseList from '../../MediaManagerModal/Releases/MMReleasesTab.svelte';
|
||||
import FilesList from '../../MediaManagerModal/LocalFiles/MMLocalFilesTab.svelte';
|
||||
import { modalStack } from '../../Modal/modal.store';
|
||||
import FileActionsModal from '../modals/FileActionsModal.svelte';
|
||||
import DownloadsList from '../DownloadList.svelte';
|
||||
import { useRequest } from '../../../stores/data.store';
|
||||
import { derived, type Readable } from 'svelte/store';
|
||||
import ReleaseActionsModal from '../modals/ReleaseActionsModal.svelte';
|
||||
import type { SonarrRelease } from '../../../apis/sonarr/sonarr-api';
|
||||
import Button from '../../Button.svelte';
|
||||
import type { FileResource } from '../../../apis/combined-types';
|
||||
|
||||
export let modalId: symbol;
|
||||
export let hidden: boolean;
|
||||
export let id: number;
|
||||
|
||||
const { promise: files, refresh: refreshFiles } = useRequest(radarrApi.getFilesByMovieId, id);
|
||||
const {
|
||||
promise: downloads,
|
||||
data: downloadsData,
|
||||
refresh: refreshDownloads
|
||||
} = useRequest(radarrApi.getDownloadsById, id);
|
||||
|
||||
const handleGrabRelease = (guid: string, indexerId: number) =>
|
||||
radarrApi
|
||||
.downloadMovie(guid, indexerId)
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
// TODO: Show error
|
||||
}
|
||||
refreshFiles(id);
|
||||
|
||||
return ok;
|
||||
})
|
||||
.finally(() => {
|
||||
radarrApi.getReleaseHistory(id).then(console.log);
|
||||
setTimeout(() => refreshDownloads(id), 8000);
|
||||
});
|
||||
const handleCancelDownload = (id: number) =>
|
||||
radarrApi.cancelDownloadRadarrMovie(id).then(() => refreshDownloads(id));
|
||||
|
||||
const grabbedReleases: Readable<Record<string, boolean>> = derived(downloadsData, ($downloads) =>
|
||||
($downloads || []).reduce((acc: Record<string, boolean>, download) => {
|
||||
acc[`${download.title}`] = true;
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
|
||||
function handleSelectRelease(release: RadarrRelease | SonarrRelease) {
|
||||
modalStack.create(
|
||||
ReleaseActionsModal,
|
||||
{
|
||||
release,
|
||||
grabRelease: handleGrabRelease
|
||||
},
|
||||
modalId
|
||||
);
|
||||
}
|
||||
|
||||
function handleSelectFile(file: FileResource) {
|
||||
modalStack.create(
|
||||
FileActionsModal,
|
||||
{
|
||||
file,
|
||||
handleDeleteFile: (id: number) => radarrApi.deleteMovieFile(id).then(() => refreshFiles(id))
|
||||
},
|
||||
modalId
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<FullScreenModal {modalId} {hidden}>
|
||||
<ManageMediaMenuLayout focusOnMount>
|
||||
<h1 slot="header">Download</h1>
|
||||
<ReleaseList
|
||||
getReleases={() => radarrApi.getReleases(id)}
|
||||
selectRelease={handleSelectRelease}
|
||||
/>
|
||||
</ManageMediaMenuLayout>
|
||||
<ManageMediaMenuLayout>
|
||||
<h1 slot="header">Local Files</h1>
|
||||
<FilesList files={$files} {handleSelectFile} />
|
||||
</ManageMediaMenuLayout>
|
||||
<ManageMediaMenuLayout>
|
||||
<h1 slot="header">Downloads</h1>
|
||||
<DownloadsList downloads={$downloads} cancelDownload={handleCancelDownload} />
|
||||
</ManageMediaMenuLayout>
|
||||
</FullScreenModal>
|
||||
@@ -1,33 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { sonarrApi, type SonarrSeason } from '../../../apis/sonarr/sonarr-api';
|
||||
import { useRequest } from '../../../stores/data.store';
|
||||
import Button from '../../Button.svelte';
|
||||
import { scrollIntoView } from '../../../selectable';
|
||||
|
||||
export let id: number;
|
||||
export let selectSeason: (seasonNumber: number) => void;
|
||||
|
||||
const { promise: sonarrSeries } = useRequest(sonarrApi.getSeriesById, id);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col -my-1">
|
||||
{#await $sonarrSeries then series}
|
||||
{#if series?.seasons}
|
||||
{#each series.seasons.filter((s) => s.seasonNumber !== 0) as season, i}
|
||||
<div class="flex-1 my-1">
|
||||
<Button
|
||||
on:clickOrSelect={() => selectSeason(season.seasonNumber || i + 1)}
|
||||
on:enter={scrollIntoView({ vertical: 64 })}
|
||||
>
|
||||
<div class="mr-2">
|
||||
Season {season.seasonNumber}
|
||||
</div>
|
||||
{#if season.statistics}
|
||||
<div class="text-zinc-400">{season.statistics.totalEpisodeCount} Episodes</div>
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
@@ -1,89 +0,0 @@
|
||||
<script lang="ts">
|
||||
import FullScreenModal from '../../Modal/FullScreenModal.svelte';
|
||||
import ManageMediaMenuLayout from '../MediaManagerMenuLayout.svelte';
|
||||
import FilesList from '../../MediaManagerModal/LocalFiles/MMLocalFilesTab.svelte';
|
||||
import { modalStack } from '../../Modal/modal.store';
|
||||
import FileActionsModal from '../modals/FileActionsModal.svelte';
|
||||
import DownloadsList from '../DownloadList.svelte';
|
||||
import { useRequest } from '../../../stores/data.store';
|
||||
import { derived, type Readable } from 'svelte/store';
|
||||
import SeasonList from './SeasonList.svelte';
|
||||
import { sonarrApi } from '../../../apis/sonarr/sonarr-api';
|
||||
import SeasonReleasesModal from './modals/EpisodeListModal.svelte';
|
||||
import type { FileResource } from '../../../apis/combined-types';
|
||||
|
||||
export let modalId: symbol;
|
||||
export let groupId: symbol;
|
||||
export let hidden: boolean;
|
||||
export let id: number;
|
||||
|
||||
const { promise: files, refresh: refreshFiles } = useRequest(sonarrApi.getFilesBySeriesId, id);
|
||||
const {
|
||||
promise: downloads,
|
||||
data: downloadsData,
|
||||
refresh: refreshDownloads
|
||||
} = useRequest(sonarrApi.getDownloadsBySeriesId, id);
|
||||
|
||||
const handleGrabRelease = (guid: string, indexerId: number) =>
|
||||
sonarrApi
|
||||
.downloadSonarrRelease(guid, indexerId)
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
// TODO: Show error
|
||||
}
|
||||
refreshFiles(id);
|
||||
|
||||
return ok;
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => refreshDownloads(id), 8000);
|
||||
});
|
||||
const handleCancelDownload = (id: number) =>
|
||||
sonarrApi.cancelDownload(id).then(() => refreshDownloads(id));
|
||||
|
||||
const grabbedReleases: Readable<Record<string, boolean>> = derived(downloadsData, ($downloads) =>
|
||||
($downloads || []).reduce((acc: Record<string, boolean>, download) => {
|
||||
acc[`${download.title}`] = true;
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
|
||||
function handleSelectSeason(seasonNumber: number) {
|
||||
modalStack.create(
|
||||
SeasonReleasesModal,
|
||||
{
|
||||
seriesId: id,
|
||||
seasonNumber,
|
||||
grabRelease: handleGrabRelease
|
||||
},
|
||||
groupId
|
||||
);
|
||||
}
|
||||
|
||||
function handleSelectFile(file: FileResource) {
|
||||
modalStack.create(
|
||||
FileActionsModal,
|
||||
{
|
||||
file,
|
||||
handleDeleteFile: (id: number) =>
|
||||
sonarrApi.deleteSonarrEpisode(id).then(() => refreshFiles(id))
|
||||
},
|
||||
groupId
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<FullScreenModal {modalId} {hidden}>
|
||||
<ManageMediaMenuLayout focusOnMount>
|
||||
<h1 slot="header">Download</h1>
|
||||
<SeasonList {id} selectSeason={handleSelectSeason} />
|
||||
</ManageMediaMenuLayout>
|
||||
<ManageMediaMenuLayout>
|
||||
<h1 slot="header">Local Files</h1>
|
||||
<FilesList files={$files} {handleSelectFile} />
|
||||
</ManageMediaMenuLayout>
|
||||
<ManageMediaMenuLayout>
|
||||
<h1 slot="header">Downloads</h1>
|
||||
<DownloadsList downloads={$downloads} cancelDownload={handleCancelDownload} />
|
||||
</ManageMediaMenuLayout>
|
||||
</FullScreenModal>
|
||||
@@ -1,77 +0,0 @@
|
||||
<script lang="ts">
|
||||
import FullScreenModal from '../../../Modal/FullScreenModal.svelte';
|
||||
import ManageMediaMenuLayout from '../../MediaManagerMenuLayout.svelte';
|
||||
import { sonarrApi, type SonarrEpisode } from '../../../../apis/sonarr/sonarr-api';
|
||||
import { useRequest } from '../../../../stores/data.store';
|
||||
import Button from '../../../Button.svelte';
|
||||
import { modalStack } from '../../../Modal/modal.store';
|
||||
import ReleaseListModal from '../../modals/ReleaseListModal.svelte';
|
||||
import ReleaseActionsModal from '../../modals/ReleaseActionsModal.svelte';
|
||||
import type { Release } from '../../../../apis/combined-types';
|
||||
|
||||
export let modalId: symbol;
|
||||
export let groupId: symbol;
|
||||
export let hidden: boolean;
|
||||
export let seriesId: number;
|
||||
export let seasonNumber: number;
|
||||
export let grabRelease: (guid: string, indexerId: number) => Promise<boolean>;
|
||||
|
||||
const { promise: episodes } = useRequest(sonarrApi.getEpisodes, seriesId, seasonNumber);
|
||||
|
||||
const handleSelectRelease = (release: Release) => {
|
||||
modalStack.create(
|
||||
ReleaseActionsModal,
|
||||
{
|
||||
release,
|
||||
grabRelease: () => grabRelease(release.guid || '', release.indexerId || -1),
|
||||
status: undefined
|
||||
},
|
||||
groupId
|
||||
);
|
||||
};
|
||||
|
||||
function handleSelectEpisode(episode: SonarrEpisode) {
|
||||
const id = episode.id;
|
||||
if (!id) return;
|
||||
modalStack.create(
|
||||
ReleaseListModal,
|
||||
{
|
||||
getReleases: () => sonarrApi.getEpisodeReleases(id),
|
||||
selectRelease: handleSelectRelease
|
||||
},
|
||||
groupId
|
||||
);
|
||||
}
|
||||
|
||||
function handleSelectSeasonPacks() {
|
||||
modalStack.create(
|
||||
ReleaseListModal,
|
||||
{
|
||||
getReleases: () => sonarrApi.getSeasonReleases(seriesId, seasonNumber),
|
||||
selectRelease: handleSelectRelease
|
||||
},
|
||||
groupId
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<FullScreenModal {modalId} {hidden}>
|
||||
<Button on:clickOrSelect={handleSelectSeasonPacks}>Season Packs</Button>
|
||||
<ManageMediaMenuLayout>
|
||||
<h1 slot="header">Episodes</h1>
|
||||
<div class="flex flex-col -my-1">
|
||||
{#await $episodes then episodes}
|
||||
{#each episodes as episode}
|
||||
<div class="my-1">
|
||||
<Button on:clickOrSelect={() => handleSelectEpisode(episode)}>
|
||||
<div class="flex items-center font-medium">
|
||||
<div class="mr-2 text-zinc-300">{episode.episodeNumber}.</div>
|
||||
<div>{episode.title}</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
{/each}
|
||||
{/await}
|
||||
</div>
|
||||
</ManageMediaMenuLayout>
|
||||
</FullScreenModal>
|
||||
@@ -1,19 +1,18 @@
|
||||
import { derived, get, writable } from 'svelte/store';
|
||||
import { type ComponentType } from 'svelte';
|
||||
import { derived, get, writable } from 'svelte/store';
|
||||
import LibraryPage from '../../pages/LibraryPage.svelte';
|
||||
import ManagePage from '../../pages/ManagePage/ManagePage.svelte';
|
||||
import MoviesHomePage from '../../pages/MoviesHomePage.svelte';
|
||||
import PageNotFound from '../../pages/PageNotFound.svelte';
|
||||
import PersonPage from '../../pages/PersonPage.svelte';
|
||||
import SearchPage from '../../pages/SearchPage.svelte';
|
||||
import SeriesHomePage from '../../pages/SeriesHomePage.svelte';
|
||||
import EpisodePage from '../../pages/TitlePages/EpisodePage.svelte';
|
||||
import { modalStack } from '../Modal/modal.store';
|
||||
import MoviesHomePage from '../../pages/MoviesHomePage.svelte';
|
||||
import LibraryPage from '../../pages/LibraryPage.svelte';
|
||||
import SearchPage from '../../pages/SearchPage.svelte';
|
||||
import PageNotFound from '../../pages/PageNotFound.svelte';
|
||||
import ManagePage from '../../pages/ManagePage/ManagePage.svelte';
|
||||
import PersonPage from '../../pages/PersonPage.svelte';
|
||||
import UsersPage from '../../pages/UsersPage.svelte';
|
||||
import StreamSelectorPage from '../../pages/TitlePages/StreamSelectorModal.svelte';
|
||||
import SeriesPage from '../../pages/TitlePages/SeriesPage/SeriesPage.svelte';
|
||||
import MoviePage from '../../pages/TitlePages/MoviePage/MoviePage.svelte';
|
||||
import SeriesPage from '../../pages/TitlePages/SeriesPage/SeriesPage.svelte';
|
||||
import UiComponents from '../../pages/UIComponents.svelte';
|
||||
import UsersPage from '../../pages/UsersPage.svelte';
|
||||
import { modalStack } from '../Modal/modal.store';
|
||||
|
||||
interface Page {
|
||||
id: symbol;
|
||||
|
||||
Reference in New Issue
Block a user