mirror of
https://github.com/Sonarr/Sonarr
synced 2026-01-23 16:02:14 +01:00
New: Manually import multiple items at the same time from Activity: Queue
This commit is contained in:
parent
ec44e1c513
commit
ce8a5d8a6b
9 changed files with 85 additions and 22 deletions
|
|
@ -26,6 +26,7 @@ import useEpisodes from 'Episode/useEpisodes';
|
|||
import { useCustomFiltersList } from 'Filters/useCustomFilters';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import { SortDirection } from 'Helpers/Props/sortDirections';
|
||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||
import { CheckInputChanged } from 'typings/inputs';
|
||||
import QueueModel from 'typings/Queue';
|
||||
import { TableOptionsChangePayload } from 'typings/Table';
|
||||
|
|
@ -109,6 +110,9 @@ function QueueContent() {
|
|||
const [isConfirmRemoveModalOpen, setIsConfirmRemoveModalOpen] =
|
||||
useState(false);
|
||||
|
||||
const [isInteractiveImportDownloadIds, setIsInteractiveImportDownloadIds] =
|
||||
useState<string[]>(() => []);
|
||||
|
||||
const isRefreshing =
|
||||
isLoading || isEpisodesFetching || isRefreshMonitoredDownloadsExecuting;
|
||||
|
||||
|
|
@ -156,12 +160,30 @@ function QueueContent() {
|
|||
shouldBlockRefresh.current = false;
|
||||
removeQueueItems({ ids: selectedIds });
|
||||
setIsConfirmRemoveModalOpen(false);
|
||||
}, [selectedIds, setIsConfirmRemoveModalOpen, removeQueueItems]);
|
||||
}, [selectedIds, removeQueueItems]);
|
||||
|
||||
const handleConfirmRemoveModalClose = useCallback(() => {
|
||||
shouldBlockRefresh.current = false;
|
||||
setIsConfirmRemoveModalOpen(false);
|
||||
}, [setIsConfirmRemoveModalOpen]);
|
||||
}, []);
|
||||
|
||||
const handleImportSelectedPress = useCallback(() => {
|
||||
shouldBlockRefresh.current = true;
|
||||
setIsInteractiveImportDownloadIds(
|
||||
selectedIds
|
||||
.map((id) => {
|
||||
const item = records.find((i) => i.id === id);
|
||||
|
||||
return item?.downloadId;
|
||||
})
|
||||
.filter((id): id is string => !!id)
|
||||
);
|
||||
}, [records, selectedIds]);
|
||||
|
||||
const handleImportSelectedModalClose = useCallback(() => {
|
||||
shouldBlockRefresh.current = false;
|
||||
setIsInteractiveImportDownloadIds([]);
|
||||
}, []);
|
||||
|
||||
const handleFilterSelect = useCallback(
|
||||
(selectedFilterKey: string | number) => {
|
||||
|
|
@ -292,6 +314,15 @@ function QueueContent() {
|
|||
isSpinning={isRemoving}
|
||||
onPress={handleRemoveSelectedPress}
|
||||
/>
|
||||
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('ImportSelected')}
|
||||
iconName={icons.INTERACTIVE}
|
||||
isDisabled={disableSelectedActions}
|
||||
onPress={handleImportSelectedPress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
<PageToolbarSection alignContent={align.RIGHT}>
|
||||
|
|
@ -358,6 +389,13 @@ function QueueContent() {
|
|||
onRemovePress={handleRemoveSelectedConfirmed}
|
||||
onModalClose={handleConfirmRemoveModalClose}
|
||||
/>
|
||||
|
||||
<InteractiveImportModal
|
||||
isOpen={isInteractiveImportDownloadIds.length > 0}
|
||||
downloadIds={isInteractiveImportDownloadIds}
|
||||
title={translate('InteractiveImportMultipleQueueItems')}
|
||||
onModalClose={handleImportSelectedModalClose}
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ interface QueueRowProps {
|
|||
id: number;
|
||||
seriesId?: number;
|
||||
episodeIds: number[];
|
||||
downloadId?: string;
|
||||
downloadId: string;
|
||||
title: string;
|
||||
status: string;
|
||||
trackedDownloadStatus?: QueueTrackedDownloadStatus;
|
||||
|
|
@ -399,7 +399,7 @@ function QueueRow(props: QueueRowProps) {
|
|||
|
||||
<InteractiveImportModal
|
||||
isOpen={isInteractiveImportModalOpen}
|
||||
downloadId={downloadId}
|
||||
downloadIds={[downloadId]}
|
||||
title={title}
|
||||
onModalClose={handleInteractiveImportModalClose}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ function isSameEpisodeFile(
|
|||
const filterExistingFilesStore = create<boolean>(() => false);
|
||||
|
||||
export interface InteractiveImportModalContentProps {
|
||||
downloadId?: string;
|
||||
downloadIds?: string[];
|
||||
seriesId?: number;
|
||||
seasonNumber?: number;
|
||||
showSeries?: boolean;
|
||||
|
|
@ -224,7 +224,7 @@ function InteractiveImportModalContentInner(
|
|||
props: InteractiveImportModalContentProps
|
||||
) {
|
||||
const {
|
||||
downloadId,
|
||||
downloadIds,
|
||||
seriesId,
|
||||
seasonNumber,
|
||||
allowSeriesChange = true,
|
||||
|
|
@ -252,7 +252,7 @@ function InteractiveImportModalContentInner(
|
|||
data,
|
||||
originalItems,
|
||||
} = useInteractiveImport({
|
||||
downloadId,
|
||||
downloadIds,
|
||||
seriesId,
|
||||
seasonNumber,
|
||||
folder,
|
||||
|
|
@ -484,7 +484,8 @@ function InteractiveImportModalContentInner(
|
|||
}, [setIsConfirmDeleteModalOpen]);
|
||||
|
||||
const handleImportSelectedPress = useCallback(() => {
|
||||
const finalImportMode = downloadId || !showImportMode ? 'auto' : importMode;
|
||||
const finalImportMode =
|
||||
downloadIds || !showImportMode ? 'auto' : importMode;
|
||||
|
||||
const existingFiles: Partial<EpisodeFile>[] = [];
|
||||
const files: InteractiveImportCommandOptions[] = [];
|
||||
|
|
@ -502,6 +503,7 @@ function InteractiveImportModalContentInner(
|
|||
|
||||
if (isSelected) {
|
||||
const {
|
||||
downloadId,
|
||||
series,
|
||||
seasonNumber,
|
||||
episodes,
|
||||
|
|
@ -605,7 +607,7 @@ function InteractiveImportModalContentInner(
|
|||
onModalClose();
|
||||
}
|
||||
}, [
|
||||
downloadId,
|
||||
downloadIds,
|
||||
showImportMode,
|
||||
importMode,
|
||||
items,
|
||||
|
|
@ -921,7 +923,7 @@ function InteractiveImportModalContentInner(
|
|||
</SpinnerButton>
|
||||
) : null}
|
||||
|
||||
{!downloadId && showImportMode ? (
|
||||
{!downloadIds && showImportMode ? (
|
||||
<SelectInput
|
||||
className={styles.importMode}
|
||||
name="importMode"
|
||||
|
|
@ -1046,9 +1048,9 @@ function InteractiveImportModalContent(
|
|||
) {
|
||||
const filterExistingFiles = filterExistingFilesStore((state) => state);
|
||||
|
||||
const { downloadId, seriesId, seasonNumber, folder } = props;
|
||||
const { downloadIds, seriesId, seasonNumber, folder } = props;
|
||||
const { data } = useInteractiveImport({
|
||||
downloadId,
|
||||
downloadIds,
|
||||
seriesId,
|
||||
seasonNumber,
|
||||
folder,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ interface InteractiveImportModalProps
|
|||
extends Omit<InteractiveImportModalContentProps, 'modalTitle'> {
|
||||
isOpen: boolean;
|
||||
folder?: string;
|
||||
downloadId?: string;
|
||||
downloadIds?: string[];
|
||||
modalTitle?: string;
|
||||
onModalClose(): void;
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ function InteractiveImportModal(props: InteractiveImportModalProps) {
|
|||
const {
|
||||
isOpen,
|
||||
folder,
|
||||
downloadId,
|
||||
downloadIds,
|
||||
modalTitle = translate('ManualImport'),
|
||||
onModalClose,
|
||||
...otherProps
|
||||
|
|
@ -54,11 +54,11 @@ function InteractiveImportModal(props: InteractiveImportModalProps) {
|
|||
closeOnBackgroundClick={false}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
{folderPath || downloadId ? (
|
||||
{folderPath || downloadIds ? (
|
||||
<InteractiveImportModalContent
|
||||
{...otherProps}
|
||||
folder={folderPath}
|
||||
downloadId={downloadId}
|
||||
downloadIds={downloadIds}
|
||||
modalTitle={modalTitle}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import ReleaseType from './ReleaseType';
|
|||
const DEFAULT_ITEMS: InteractiveImport[] = [];
|
||||
|
||||
interface InteractiveImportParams {
|
||||
downloadId?: string;
|
||||
downloadIds?: string[];
|
||||
seriesId?: number;
|
||||
seasonNumber?: number;
|
||||
folder?: string;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export interface QueryParams {
|
|||
| boolean
|
||||
| PropertyFilter[]
|
||||
| number[]
|
||||
| string[]
|
||||
| undefined;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -976,6 +976,7 @@
|
|||
"ImportMechanismHandlingDisabledHealthCheckMessage": "Enable Completed Download Handling",
|
||||
"ImportScriptPath": "Import Script Path",
|
||||
"ImportScriptPathHelpText": "The path to the script to use for importing",
|
||||
"ImportSelected": "Import Selected",
|
||||
"ImportSeries": "Import Series",
|
||||
"ImportUsingScript": "Import Using Script",
|
||||
"ImportUsingScriptHelpText": "Copy files for importing using a script (ex. for transcoding)",
|
||||
|
|
@ -1086,6 +1087,7 @@
|
|||
"InstanceNameHelpText": "Instance name in tab and for Syslog app name",
|
||||
"InteractiveImport": "Interactive Import",
|
||||
"InteractiveImportLoadError": "Unable to load manual import items",
|
||||
"InteractiveImportMultipleQueueItems": "Multiple Queue Items",
|
||||
"InteractiveImportNoEpisode": "One or more episodes must be chosen for each selected file",
|
||||
"InteractiveImportNoFilesFound": "No video files were found in the selected folder",
|
||||
"InteractiveImportNoImportMode": "An import mode must be selected",
|
||||
|
|
|
|||
|
|
@ -20,14 +20,34 @@ public ManualImportController(IManualImportService manualImportService)
|
|||
|
||||
[HttpGet]
|
||||
[Produces("application/json")]
|
||||
public List<ManualImportResource> GetMediaFiles(string? folder, string? downloadId, int? seriesId, int? seasonNumber, bool filterExistingFiles = true)
|
||||
public List<ManualImportResource> GetMediaFiles(string? folder, [FromQuery] string[]? downloadIds, int? seriesId, int? seasonNumber, bool filterExistingFiles = true)
|
||||
{
|
||||
if (seriesId.HasValue && downloadId.IsNullOrWhiteSpace())
|
||||
if (seriesId.HasValue && downloadIds == null)
|
||||
{
|
||||
return _manualImportService.GetMediaFiles(seriesId.Value, seasonNumber).ToResource().Select(AddQualityWeight).ToList();
|
||||
return _manualImportService.GetMediaFiles(seriesId.Value, seasonNumber)
|
||||
.ToResource()
|
||||
.Select(AddQualityWeight)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return _manualImportService.GetMediaFiles(folder, downloadId, seriesId, filterExistingFiles).ToResource().Select(AddQualityWeight).ToList();
|
||||
if (downloadIds != null && downloadIds.Any())
|
||||
{
|
||||
var files = new List<ManualImportItem>();
|
||||
|
||||
foreach (var downloadId in downloadIds.Distinct())
|
||||
{
|
||||
files.AddRange(_manualImportService.GetMediaFiles(null, downloadId, seriesId, filterExistingFiles));
|
||||
}
|
||||
|
||||
return files.ToResource()
|
||||
.Select(AddQualityWeight)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return _manualImportService.GetMediaFiles(folder, null, seriesId, filterExistingFiles)
|
||||
.ToResource()
|
||||
.Select(AddQualityWeight)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ public static ManualImportResource ToResource(this ManualImportItem model)
|
|||
Size = model.Size,
|
||||
Series = model.Series?.ToResource(),
|
||||
SeasonNumber = model.SeasonNumber,
|
||||
Episodes = model.Episodes.ToResource(),
|
||||
Episodes = model.Episodes?.ToResource() ?? [],
|
||||
EpisodeFileId = model.EpisodeFileId,
|
||||
ReleaseGroup = model.ReleaseGroup,
|
||||
Quality = model.Quality,
|
||||
|
|
|
|||
Loading…
Reference in a new issue