feat: Bulk delete local files and downloads, confirm dialogs
This commit is contained in:
@@ -150,7 +150,7 @@ export class SonarrApi implements Api<paths> {
|
||||
.then((r) => r.data);
|
||||
};
|
||||
|
||||
cancelDownloadSonarrEpisode = async (downloadId: number) => {
|
||||
cancelDownload = async (downloadId: number) => {
|
||||
const deleteResponse = await this.getClient()
|
||||
?.DELETE('/api/v3/queue/{id}', {
|
||||
params: {
|
||||
@@ -168,6 +168,15 @@ export class SonarrApi implements Api<paths> {
|
||||
return !!deleteResponse?.response.ok;
|
||||
};
|
||||
|
||||
cancelDownloads = async (downloadIds: number[]) =>
|
||||
this.getClient()
|
||||
?.DELETE('/api/v3/queue/bulk', {
|
||||
body: {
|
||||
ids: downloadIds
|
||||
}
|
||||
})
|
||||
.then((r) => r.response.ok) || Promise.resolve(false);
|
||||
|
||||
downloadSonarrRelease = (guid: string, indexerId: number) =>
|
||||
this.getClient()
|
||||
?.POST('/api/v3/release', {
|
||||
|
||||
@@ -27,12 +27,12 @@
|
||||
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (event.repeat) return;
|
||||
if (event.key === 'Shift') showOverlay = !showOverlay;
|
||||
if (event.key === 'Shift') showOverlay = true;
|
||||
};
|
||||
|
||||
const handleKeyup = (event: KeyboardEvent) => {
|
||||
if (event.repeat) return;
|
||||
if (event.key === 'Shift') showOverlay = !showOverlay;
|
||||
if (event.key === 'Shift') showOverlay = false;
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
<script lang="ts">
|
||||
import ButtonGhost from '../Ghosts/ButtonGhost.svelte';
|
||||
import type { FileResource } from '../../apis/combined-types';
|
||||
import Table from '../Table/Table.svelte';
|
||||
import MMLocalFileRow from '../MediaManagerModal/MMLocalFileRow.svelte';
|
||||
import TableHeaderRow from '../Table/TableHeaderRow.svelte';
|
||||
import TableHeaderSortBy from '../Table/TableHeaderSortBy.svelte';
|
||||
import TableHeaderCell from '../Table/TableHeaderCell.svelte';
|
||||
import Container from '../../../Container.svelte';
|
||||
import Button from '../Button.svelte';
|
||||
import { Trash } from 'radix-icons-svelte';
|
||||
import { scrollIntoView } from '../../selectable';
|
||||
import type { DeleteFile } from '../MediaManagerModal/MediaManagerModal';
|
||||
import { modalStack } from '../Modal/modal.store';
|
||||
import MMConfirmDeleteFileDialog from '../MediaManagerModal/Dialogs/MMConfirmDeleteFileDialog.svelte';
|
||||
import { sonarrApi } from '../../apis/sonarr/sonarr-api';
|
||||
|
||||
export let files: Promise<FileResource[]>;
|
||||
export let deleteFile: DeleteFile;
|
||||
// export let handleSelectFile: (file: FileResource) => void;
|
||||
|
||||
let sortBy: 'size' | 'quality' | 'title' | 'runtime' | undefined = 'title';
|
||||
let sortDirection: 'asc' | 'desc' = 'desc';
|
||||
|
||||
const toggleSortBy = (sort: typeof sortBy) => () => {
|
||||
if (sortBy === sort) {
|
||||
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
sortBy = sort;
|
||||
sortDirection = 'desc';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#await files}
|
||||
{#each new Array(5) as _, index}
|
||||
<div class="flex-1 my-2">
|
||||
<ButtonGhost />
|
||||
</div>
|
||||
{/each}
|
||||
{:then files}
|
||||
<div class="grid grid-cols-[1fr_max-content_max-content_max-content_max-content] gap-y-4">
|
||||
<TableHeaderRow>
|
||||
<TableHeaderSortBy
|
||||
icon={sortBy === 'title' ? sortDirection : undefined}
|
||||
on:clickOrSelect={toggleSortBy('title')}>Title</TableHeaderSortBy
|
||||
>
|
||||
<TableHeaderSortBy
|
||||
icon={sortBy === 'runtime' ? sortDirection : undefined}
|
||||
on:clickOrSelect={toggleSortBy('runtime')}>Runtime</TableHeaderSortBy
|
||||
>
|
||||
<TableHeaderSortBy
|
||||
icon={sortBy === 'size' ? sortDirection : undefined}
|
||||
on:clickOrSelect={toggleSortBy('size')}>Size</TableHeaderSortBy
|
||||
>
|
||||
<TableHeaderSortBy
|
||||
icon={sortBy === 'quality' ? sortDirection : undefined}
|
||||
on:clickOrSelect={toggleSortBy('quality')}>Quality</TableHeaderSortBy
|
||||
>
|
||||
<TableHeaderCell />
|
||||
</TableHeaderRow>
|
||||
{#each files as file}
|
||||
<MMLocalFileRow {file} {deleteFile} />
|
||||
{/each}
|
||||
</div>
|
||||
{#if files?.length}
|
||||
<Container
|
||||
direction="horizontal"
|
||||
class="flex mt-8 mx-12"
|
||||
on:enter={scrollIntoView({ vertical: 128 })}
|
||||
>
|
||||
<Button
|
||||
on:clickOrSelect={() =>
|
||||
modalStack.create(MMConfirmDeleteFileDialog, {
|
||||
deleteFile: () => sonarrApi.deleteSonarrEpisodes(files.map((f) => f.id || -1))
|
||||
})}
|
||||
>
|
||||
Delete all
|
||||
<Trash size={19} slot="icon" />
|
||||
</Button>
|
||||
</Container>
|
||||
{:else}
|
||||
<div class="text-zinc-400 font-medium mx-12 flex flex-col items-center justify-center h-full">
|
||||
<h1 class="text-xl text-zinc-300">No local files found</h1>
|
||||
<div>Your local files will appear here.</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import FullScreenModal from '../../Modal/FullScreenModal.svelte';
|
||||
import ReleaseList from '../ReleaseList.svelte';
|
||||
import ReleaseList from '../../MediaManagerModal/Releases/MMReleasesTab.svelte';
|
||||
import type { Release } from '../../../apis/combined-types';
|
||||
|
||||
export let modalId: symbol;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
radarrApi,
|
||||
type RadarrRelease
|
||||
} from '../../../apis/radarr/radarr-api';
|
||||
import ReleaseList from '../ReleaseList.svelte';
|
||||
import FilesList from '../FileList.svelte';
|
||||
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';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import FullScreenModal from '../../Modal/FullScreenModal.svelte';
|
||||
import ManageMediaMenuLayout from '../MediaManagerMenuLayout.svelte';
|
||||
import FilesList from '../FileList.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';
|
||||
@@ -39,7 +39,7 @@
|
||||
setTimeout(() => refreshDownloads(id), 8000);
|
||||
});
|
||||
const handleCancelDownload = (id: number) =>
|
||||
sonarrApi.cancelDownloadSonarrEpisode(id).then(() => refreshDownloads(id));
|
||||
sonarrApi.cancelDownload(id).then(() => refreshDownloads(id));
|
||||
|
||||
const grabbedReleases: Readable<Record<string, boolean>> = derived(downloadsData, ($downloads) =>
|
||||
($downloads || []).reduce((acc: Record<string, boolean>, download) => {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<script lang="ts">
|
||||
import TableButton from '../../Table/TableButton.svelte';
|
||||
import { Cross1 } from 'radix-icons-svelte';
|
||||
import type { Download } from '../../../apis/combined-types';
|
||||
import type { CancelDownloadFn } from '../MediaManagerModal';
|
||||
import { scrollIntoView } from '../../../selectable';
|
||||
import Container from '../../../../Container.svelte';
|
||||
import classNames from 'classnames';
|
||||
import { modalStack } from '../../Modal/modal.store';
|
||||
import MMConfirmDeleteFileDialog from '../Dialogs/MMConfirmDeleteFileDialog.svelte';
|
||||
|
||||
export let download: Download;
|
||||
export let cancelDownload: CancelDownloadFn;
|
||||
|
||||
let title = '';
|
||||
let subtitle = '';
|
||||
|
||||
function handleCancelDownload() {
|
||||
modalStack.create(MMConfirmDeleteFileDialog, {
|
||||
deleteFile: () => cancelDownload(download.id || -1)
|
||||
});
|
||||
}
|
||||
|
||||
$: {
|
||||
if ('series' in download && 'episode' in download) {
|
||||
title = download.episode?.title || '';
|
||||
subtitle = `Episode ${download.episode?.episodeNumber || ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
$: console.log('download', download);
|
||||
</script>
|
||||
|
||||
<Container class="contents" let:hasFocusWithin>
|
||||
<div
|
||||
class={classNames(
|
||||
'flex items-center justify-between relative overflow-hidden',
|
||||
'px-6 py-3 bg-secondary-800 border-2 border-secondary-800 rounded-xl',
|
||||
{
|
||||
'bg-secondary-900 border-secondary-500': hasFocusWithin
|
||||
}
|
||||
)}
|
||||
>
|
||||
<!-- Background -->
|
||||
<div
|
||||
class="absolute inset-y-0 bg-secondary-50/10 left-0"
|
||||
style={`width: ${
|
||||
(((download.size || download.sizeleft || 0) - (download.sizeleft || 0)) /
|
||||
(download.size || 1)) *
|
||||
100
|
||||
}%`}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<h2 class="text-zinc-300 font-medium">{subtitle}</h2>
|
||||
<h1 class="text-lg font-medium tracking-wide">{title}</h1>
|
||||
</div>
|
||||
<div>
|
||||
<TableButton
|
||||
on:clickOrSelect={handleCancelDownload}
|
||||
on:enter={scrollIntoView({ vertical: 128 })}
|
||||
>
|
||||
<Cross1 size={19} />
|
||||
</TableButton>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
@@ -2,10 +2,10 @@
|
||||
import { sonarrApi } from '../../apis/sonarr/sonarr-api';
|
||||
import MMMainLayout from './MMMainLayout.svelte';
|
||||
import MMAddToSonarr from './MMAddToSonarr.svelte';
|
||||
import MMModal from '../MediaManager/MMModal.svelte';
|
||||
import ReleaseList from '../MediaManager/ReleaseList.svelte';
|
||||
import MMModal from './MMModal.svelte';
|
||||
import ReleaseList from './Releases/MMReleasesTab.svelte';
|
||||
import DownloadList from '../MediaManager/DownloadList.svelte';
|
||||
import FileList from '../MediaManager/FileList.svelte';
|
||||
import FileList from './LocalFiles/MMLocalFilesTab.svelte';
|
||||
import { log } from '../../utils';
|
||||
|
||||
export let id: number; // Tmdb ID
|
||||
@@ -32,7 +32,7 @@
|
||||
const getReleases = () => sonarrEpisode.then((se) => sonarrApi.getEpisodeReleases(se?.id || -1));
|
||||
const selectRelease = () => {};
|
||||
|
||||
const cancelDownload = sonarrApi.cancelDownloadSonarrEpisode;
|
||||
const cancelDownload = sonarrApi.cancelDownload;
|
||||
const handleSelectFile = () => {};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
<script lang="ts">
|
||||
import TableRow from '../Table/TableRow.svelte';
|
||||
import { scrollIntoView } from '../../selectable';
|
||||
import type { FileResource } from '../../apis/combined-types';
|
||||
import { formatSize } from '../../utils';
|
||||
import TableButton from '../Table/TableButton.svelte';
|
||||
import TableRow from '../../Table/TableRow.svelte';
|
||||
import { scrollIntoView } from '../../../selectable';
|
||||
import type { FileResource } from '../../../apis/combined-types';
|
||||
import { formatSize } from '../../../utils';
|
||||
import TableButton from '../../Table/TableButton.svelte';
|
||||
import { Trash } from 'radix-icons-svelte';
|
||||
import TableCell from '../Table/TableCell.svelte';
|
||||
import type { DeleteFile } from './MediaManagerModal';
|
||||
import { modalStack } from '../Modal/modal.store';
|
||||
import MMConfirmDeleteFileDialog from './Dialogs/MMConfirmDeleteFileDialog.svelte';
|
||||
import TableCell from '../../Table/TableCell.svelte';
|
||||
import type { DeleteFileFn } from '../MediaManagerModal';
|
||||
import { modalStack } from '../../Modal/modal.store';
|
||||
import MMConfirmDeleteFileDialog from '../Dialogs/MMConfirmDeleteFileDialog.svelte';
|
||||
|
||||
export let file: FileResource;
|
||||
export let deleteFile: DeleteFile;
|
||||
console.log(file);
|
||||
export let deleteFile: DeleteFileFn;
|
||||
</script>
|
||||
|
||||
<TableRow class="font-medium">
|
||||
@@ -0,0 +1,128 @@
|
||||
<script lang="ts">
|
||||
import ButtonGhost from '../../Ghosts/ButtonGhost.svelte';
|
||||
import type { Download, FileResource } from '../../../apis/combined-types';
|
||||
import MMLocalFileRow from './MMLocalFileRow.svelte';
|
||||
import TableHeaderRow from '../../Table/TableHeaderRow.svelte';
|
||||
import TableHeaderSortBy from '../../Table/TableHeaderSortBy.svelte';
|
||||
import TableHeaderCell from '../../Table/TableHeaderCell.svelte';
|
||||
import Container from '../../../../Container.svelte';
|
||||
import Button from '../../Button.svelte';
|
||||
import { Cross1, Trash } from 'radix-icons-svelte';
|
||||
import { scrollIntoView } from '../../../selectable';
|
||||
import type {
|
||||
CancelDownloadFn,
|
||||
CancelDownloadsFn,
|
||||
DeleteFileFn,
|
||||
DeleteFilesFn
|
||||
} from '../MediaManagerModal';
|
||||
import { modalStack } from '../../Modal/modal.store';
|
||||
import MMConfirmDeleteFileDialog from '../Dialogs/MMConfirmDeleteFileDialog.svelte';
|
||||
import MMDownloadRow from '../Downloads/MMDownloadRow.svelte';
|
||||
|
||||
export let files: Promise<FileResource[]>;
|
||||
export let deleteFile: DeleteFileFn;
|
||||
export let deleteFiles: DeleteFilesFn;
|
||||
|
||||
export let downloads: Promise<Download[]>;
|
||||
export let cancelDownload: CancelDownloadFn;
|
||||
export let cancelDownloads: CancelDownloadsFn;
|
||||
|
||||
let sortBy: 'size' | 'quality' | 'title' | 'runtime' | undefined = 'title';
|
||||
let sortDirection: 'asc' | 'desc' = 'desc';
|
||||
|
||||
const toggleSortBy = (sort: typeof sortBy) => () => {
|
||||
if (sortBy === sort) {
|
||||
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
sortBy = sort;
|
||||
sortDirection = 'desc';
|
||||
}
|
||||
};
|
||||
|
||||
function confirmCancelAllDownloads(downloads: Download[]) {
|
||||
modalStack.create(MMConfirmDeleteFileDialog, {
|
||||
deleteFile: () => cancelDownloads(downloads.map((f) => f.id || -1))
|
||||
});
|
||||
}
|
||||
|
||||
function confirmDeleteAllFiles(files: FileResource[]) {
|
||||
modalStack.create(MMConfirmDeleteFileDialog, {
|
||||
deleteFile: () => deleteFiles(files.map((f) => f.id || -1))
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await downloads then downloads}
|
||||
{#if downloads?.length}
|
||||
<h1 class="text-lg font-semibold tracking-wide text-zinc-300 mb-4 mx-12">Downloads</h1>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mx-12 mb-4">
|
||||
{#each downloads as download}
|
||||
<MMDownloadRow {download} {cancelDownload} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<Container
|
||||
direction="horizontal"
|
||||
class="flex mb-16 mx-12"
|
||||
on:enter={scrollIntoView({ vertical: 128 })}
|
||||
>
|
||||
<Button on:clickOrSelect={() => confirmCancelAllDownloads(downloads)}>
|
||||
Cancel All Downloads
|
||||
<Cross1 size={19} slot="icon" />
|
||||
</Button>
|
||||
</Container>
|
||||
|
||||
<h1 class="text-2xl font-semibold mb-4 mt-8 mx-12">Local Files</h1>
|
||||
{/if}
|
||||
{/await}
|
||||
|
||||
{#await files}
|
||||
{#each new Array(5) as _, index}
|
||||
<div class="flex-1 my-2">
|
||||
<ButtonGhost />
|
||||
</div>
|
||||
{/each}
|
||||
{:then files}
|
||||
<div class="grid grid-cols-[1fr_max-content_max-content_max-content_max-content] gap-y-4">
|
||||
<TableHeaderRow>
|
||||
<TableHeaderSortBy
|
||||
icon={sortBy === 'title' ? sortDirection : undefined}
|
||||
on:clickOrSelect={toggleSortBy('title')}>Title</TableHeaderSortBy
|
||||
>
|
||||
<TableHeaderSortBy
|
||||
icon={sortBy === 'runtime' ? sortDirection : undefined}
|
||||
on:clickOrSelect={toggleSortBy('runtime')}>Runtime</TableHeaderSortBy
|
||||
>
|
||||
<TableHeaderSortBy
|
||||
icon={sortBy === 'size' ? sortDirection : undefined}
|
||||
on:clickOrSelect={toggleSortBy('size')}>Size</TableHeaderSortBy
|
||||
>
|
||||
<TableHeaderSortBy
|
||||
icon={sortBy === 'quality' ? sortDirection : undefined}
|
||||
on:clickOrSelect={toggleSortBy('quality')}>Quality</TableHeaderSortBy
|
||||
>
|
||||
<TableHeaderCell />
|
||||
</TableHeaderRow>
|
||||
{#each files as file}
|
||||
<MMLocalFileRow {file} {deleteFile} />
|
||||
{/each}
|
||||
</div>
|
||||
{#if files?.length}
|
||||
<Container
|
||||
direction="horizontal"
|
||||
class="flex mt-8 mx-12"
|
||||
on:enter={scrollIntoView({ vertical: 128 })}
|
||||
>
|
||||
<Button on:clickOrSelect={() => confirmDeleteAllFiles(files)}>
|
||||
Delete all
|
||||
<Trash size={19} slot="icon" />
|
||||
</Button>
|
||||
</Container>
|
||||
{:else}
|
||||
<div class="text-zinc-400 font-medium mx-12 flex flex-col items-center justify-center h-full">
|
||||
<h1 class="text-xl text-zinc-300">No local files found</h1>
|
||||
<div>Your local files will appear here.</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
@@ -6,7 +6,7 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col h-screen">
|
||||
<div class="flex items-center pb-8 mb-8 pt-16 px-28">
|
||||
<div class="flex items-center pb-8 mb-8 pt-16 px-32">
|
||||
<div class="flex-1">
|
||||
<div class="text-4xl font-semibold">
|
||||
<slot name="title" />
|
||||
@@ -20,30 +20,30 @@
|
||||
<slot name="downloads" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-8 mx-28">
|
||||
<h1
|
||||
<div class="flex mb-8 mx-32">
|
||||
<button
|
||||
class={classNames('text-2xl font-semibold mr-8 transition-opacity cursor-pointer', {
|
||||
'opacity-40': activeTab !== 'releases'
|
||||
})}
|
||||
on:click={() => (activeTab = 'releases')}
|
||||
>
|
||||
Releases
|
||||
</h1>
|
||||
<h1
|
||||
</button>
|
||||
<button
|
||||
class={classNames('text-2xl font-semibold mr-8 transition-opacity cursor-pointer', {
|
||||
'opacity-40': activeTab !== 'local-files'
|
||||
})}
|
||||
on:click={() => (activeTab = 'local-files')}
|
||||
>
|
||||
Local Files
|
||||
</h1>
|
||||
</button>
|
||||
</div>
|
||||
<Container focusOnMount direction="horizontal" class="flex-1 grid grid-cols-1 min-h-0">
|
||||
<Container
|
||||
focusOnMount
|
||||
on:enter={() => (activeTab = 'releases')}
|
||||
class={classNames(
|
||||
'row-start-1 col-start-1 pb-16 mx-16',
|
||||
'row-start-1 col-start-1 pb-16 mx-20',
|
||||
'transition-all overflow-y-auto overflow-x-hidden scrollbar-hide',
|
||||
{
|
||||
'opacity-30 -translate-x-full': activeTab !== 'releases'
|
||||
@@ -55,7 +55,7 @@
|
||||
<Container
|
||||
on:enter={() => (activeTab = 'local-files')}
|
||||
class={classNames(
|
||||
'row-start-1 col-start-1 pb-16 mx-16',
|
||||
'row-start-1 col-start-1 pb-16 mx-20',
|
||||
'transition-all overflow-y-auto overflow-x-hidden scrollbar-hide',
|
||||
{
|
||||
'opacity-30 translate-x-full': activeTab !== 'local-files'
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
focusOnMount
|
||||
trapFocus
|
||||
class={classNames(
|
||||
'fixed inset-0 overflow-x-hidden overflow-y-auto',
|
||||
'fixed inset-0 overflow-hidden',
|
||||
{
|
||||
'opacity-0': hidden
|
||||
},
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { Release } from '../../apis/combined-types';
|
||||
|
||||
export type GrabRelease = (release: Release) => Promise<boolean>;
|
||||
export type DeleteFile = (id: number) => Promise<boolean>;
|
||||
export type GrabReleaseFn = (release: Release) => Promise<boolean>;
|
||||
export type DeleteFileFn = (id: number) => Promise<boolean>;
|
||||
export type DeleteFilesFn = (ids: number[]) => Promise<boolean>;
|
||||
export type CancelDownloadFn = (downloadId: number) => Promise<boolean>;
|
||||
export type CancelDownloadsFn = (downloadIds: number[]) => Promise<boolean>;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import MMMainLayout from './MMMainLayout.svelte';
|
||||
import MMAddToSonarr from './MMAddToSonarr.svelte';
|
||||
import MMModal from '../MediaManager/MMModal.svelte';
|
||||
import ReleaseList from '../MediaManager/ReleaseList.svelte';
|
||||
import MMModal from './MMModal.svelte';
|
||||
import ReleaseList from './Releases/MMReleasesTab.svelte';
|
||||
import DownloadList from '../MediaManager/DownloadList.svelte';
|
||||
import FileList from '../MediaManager/FileList.svelte';
|
||||
import FileList from './LocalFiles/MMLocalFilesTab.svelte';
|
||||
import { radarrApi } from '../../apis/radarr/radarr-api';
|
||||
|
||||
export let id: number; // Tmdb ID
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { formatMinutesToTime, formatSize } from '../../utils.js';
|
||||
import type { RadarrRelease } from '../../apis/radarr/radarr-api';
|
||||
import type { SonarrRelease } from '../../apis/sonarr/sonarr-api';
|
||||
import { scrollIntoView } from '../../selectable';
|
||||
import { formatMinutesToTime, formatSize } from '../../../utils.js';
|
||||
import type { RadarrRelease } from '../../../apis/radarr/radarr-api';
|
||||
import type { SonarrRelease } from '../../../apis/sonarr/sonarr-api';
|
||||
import { scrollIntoView } from '../../../selectable';
|
||||
import { Check, Download } from 'radix-icons-svelte';
|
||||
import TableRow from '../Table/TableRow.svelte';
|
||||
import type { GrabRelease } from './MediaManagerModal';
|
||||
import TableButton from '../Table/TableButton.svelte';
|
||||
import TableCell from '../Table/TableCell.svelte';
|
||||
import TableRow from '../../Table/TableRow.svelte';
|
||||
import type { GrabReleaseFn } from '../MediaManagerModal';
|
||||
import TableButton from '../../Table/TableButton.svelte';
|
||||
import TableCell from '../../Table/TableCell.svelte';
|
||||
export let release: RadarrRelease | SonarrRelease;
|
||||
|
||||
export let grabRelease: GrabRelease;
|
||||
export let grabRelease: GrabReleaseFn;
|
||||
let fetching = false;
|
||||
let didGrab = false;
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { type RadarrRelease } from '../../apis/radarr/radarr-api';
|
||||
import ButtonGhost from '../Ghosts/ButtonGhost.svelte';
|
||||
import type { SonarrRelease } from '../../apis/sonarr/sonarr-api';
|
||||
import MMReleaseListRow from '../MediaManagerModal/MMReleaseListRow.svelte';
|
||||
import Table from '../Table/Table.svelte';
|
||||
import TableHeaderRow from '../Table/TableHeaderRow.svelte';
|
||||
import TableHeaderSortBy from '../Table/TableHeaderSortBy.svelte';
|
||||
import type { GrabRelease } from '../MediaManagerModal/MediaManagerModal';
|
||||
import Container from '../../../Container.svelte';
|
||||
import TableHeaderCell from '../Table/TableHeaderCell.svelte';
|
||||
import { type RadarrRelease } from '../../../apis/radarr/radarr-api';
|
||||
import ButtonGhost from '../../Ghosts/ButtonGhost.svelte';
|
||||
import type { SonarrRelease } from '../../../apis/sonarr/sonarr-api';
|
||||
import MMReleaseListRow from './MMReleaseListRow.svelte';
|
||||
import TableHeaderRow from '../../Table/TableHeaderRow.svelte';
|
||||
import TableHeaderSortBy from '../../Table/TableHeaderSortBy.svelte';
|
||||
import type { GrabReleaseFn } from '../MediaManagerModal';
|
||||
import Container from '../../../../Container.svelte';
|
||||
import TableHeaderCell from '../../Table/TableHeaderCell.svelte';
|
||||
|
||||
type Release = RadarrRelease | SonarrRelease;
|
||||
|
||||
export let releases: Promise<Release[]>;
|
||||
export let grabRelease: GrabRelease;
|
||||
export let grabRelease: GrabReleaseFn;
|
||||
|
||||
let sortBy: 'size' | 'quality' | 'seeders' | 'age' | undefined = 'seeders';
|
||||
let sortDirection: 'asc' | 'desc' = 'desc';
|
||||
@@ -2,12 +2,18 @@
|
||||
import { sonarrApi } from '../../apis/sonarr/sonarr-api';
|
||||
import MMMainLayout from './MMMainLayout.svelte';
|
||||
import MMAddToSonarr from './MMAddToSonarr.svelte';
|
||||
import MMModal from '../MediaManager/MMModal.svelte';
|
||||
import ReleaseList from '../MediaManager/ReleaseList.svelte';
|
||||
import DownloadList from '../MediaManager/DownloadList.svelte';
|
||||
import FileList from '../MediaManager/FileList.svelte';
|
||||
import type { DeleteFile, GrabRelease } from './MediaManagerModal';
|
||||
import MMModal from './MMModal.svelte';
|
||||
import MMReleasesTab from './Releases/MMReleasesTab.svelte';
|
||||
import MMLocalFilesTab from './LocalFiles/MMLocalFilesTab.svelte';
|
||||
import type {
|
||||
CancelDownloadFn,
|
||||
CancelDownloadsFn,
|
||||
DeleteFileFn,
|
||||
DeleteFilesFn,
|
||||
GrabReleaseFn
|
||||
} from './MediaManagerModal';
|
||||
import type { Release } from '../../apis/combined-types';
|
||||
import { onDestroy } from 'svelte';
|
||||
|
||||
export let id: number; // Tmdb ID
|
||||
export let season: number;
|
||||
@@ -15,18 +21,59 @@
|
||||
export let hidden: boolean;
|
||||
|
||||
const sonarrItem = sonarrApi.getSeriesByTmdbId(id);
|
||||
const downloads = sonarrItem.then((si) => sonarrApi.getDownloadsBySeriesId(si?.id || -1));
|
||||
const files = sonarrItem.then((si) => sonarrApi.getFilesBySeriesId(si?.id || -1));
|
||||
|
||||
// Releases
|
||||
const releases: Promise<Release[]> = sonarrItem.then((si) =>
|
||||
sonarrApi.getSeasonReleases(si?.id || -1, season)
|
||||
);
|
||||
const grabRelease: GrabRelease = (release) =>
|
||||
sonarrApi.downloadSonarrRelease(release.guid || '', release.indexerId || -1);
|
||||
let releases: Promise<Release[]> = getReleases();
|
||||
let files = getLocalFiles();
|
||||
let downloads = getDownloads();
|
||||
|
||||
const cancelDownload = sonarrApi.cancelDownloadSonarrEpisode;
|
||||
const deleteFile: DeleteFile = sonarrApi.deleteSonarrEpisode;
|
||||
let refreshDownloadsTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const grabRelease: GrabReleaseFn = (release) =>
|
||||
sonarrApi.downloadSonarrRelease(release.guid || '', release.indexerId || -1).then((r) => {
|
||||
refreshDownloadsTimeout = setTimeout(() => {
|
||||
downloads = getDownloads();
|
||||
}, 8000);
|
||||
return r;
|
||||
});
|
||||
|
||||
const deleteFile: DeleteFileFn = (...args) =>
|
||||
sonarrApi.deleteSonarrEpisode(...args).then((r) => {
|
||||
files = getLocalFiles();
|
||||
return r;
|
||||
});
|
||||
const deleteFiles: DeleteFilesFn = (...args) =>
|
||||
sonarrApi.deleteSonarrEpisodes(...args).then((r) => {
|
||||
files = getLocalFiles();
|
||||
return r;
|
||||
});
|
||||
const cancelDownload: CancelDownloadFn = (...args) =>
|
||||
sonarrApi.cancelDownload(...args).then((r) => {
|
||||
downloads = getDownloads();
|
||||
return r;
|
||||
});
|
||||
const cancelDownloads: CancelDownloadsFn = (...args) =>
|
||||
sonarrApi.cancelDownloads(...args).then((r) => {
|
||||
downloads = getDownloads();
|
||||
return r;
|
||||
});
|
||||
|
||||
function getReleases() {
|
||||
return sonarrItem.then((si) => sonarrApi.getSeasonReleases(si?.id || -1, season));
|
||||
}
|
||||
|
||||
function getLocalFiles() {
|
||||
return sonarrItem.then((si) => sonarrApi.getFilesBySeriesId(si?.id || -1));
|
||||
}
|
||||
|
||||
function getDownloads() {
|
||||
return sonarrItem
|
||||
.then((si) => sonarrApi.getDownloadsBySeriesId(si?.id || -1))
|
||||
.then((ds) => ds.filter((d) => d.episode?.seasonNumber === season));
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
clearTimeout(refreshDownloadsTimeout);
|
||||
});
|
||||
</script>
|
||||
|
||||
<MMModal {modalId} {hidden}>
|
||||
@@ -37,9 +84,17 @@
|
||||
<MMMainLayout>
|
||||
<h1 slot="title">{series?.title}</h1>
|
||||
<h2 slot="subtitle">Season {season} Packs</h2>
|
||||
<ReleaseList slot="releases" {releases} {grabRelease} />
|
||||
<FileList slot="local-files" {files} {deleteFile} />
|
||||
<DownloadList slot="downloads" {downloads} {cancelDownload} />
|
||||
<MMReleasesTab slot="releases" {releases} {grabRelease} />
|
||||
<MMLocalFilesTab
|
||||
slot="local-files"
|
||||
{files}
|
||||
{deleteFile}
|
||||
{deleteFiles}
|
||||
{downloads}
|
||||
{cancelDownload}
|
||||
{cancelDownloads}
|
||||
/>
|
||||
<!-- <DownloadList slot="downloads" {downloads} {cancelDownload} />-->
|
||||
</MMMainLayout>
|
||||
{/if}
|
||||
{/await}
|
||||
|
||||
Reference in New Issue
Block a user