Skip to content

Commit

Permalink
Add quick select option to release management (#900)
Browse files Browse the repository at this point in the history
  • Loading branch information
harshithmohan authored May 8, 2024
1 parent 699afd6 commit e9f19f8
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,13 @@ type Props = {
ignoreVariations: boolean;
onlyFinishedSeries: boolean;
setSelectedEpisode: (episode: EpisodeType) => void;
setSelectedSeriesId: (id: number) => void;
setSeriesCount: (count: number) => void;
};

const MultiplesUtilList = ({ ignoreVariations, onlyFinishedSeries, setSelectedEpisode, setSeriesCount }: Props) => {
const MultiplesUtilList = (
{ ignoreVariations, onlyFinishedSeries, setSelectedEpisode, setSelectedSeriesId, setSeriesCount }: Props,
) => {
const [selectedSeries, setSelectedSeries] = useState(0);

const seriesQuery = useSeriesWithMultipleReleases({ ignoreVariations, onlyFinishedSeries, pageSize: 25 });
Expand All @@ -124,8 +127,9 @@ const MultiplesUtilList = ({ ignoreVariations, onlyFinishedSeries, setSelectedEp
const [episodes, episodeCount] = useFlattenListResult(episodesQuery.data);

useEffect(() => {
setSelectedSeriesId(selectedSeries);
setSeriesCount(seriesCount);
}, [seriesCount, setSeriesCount]);
}, [selectedSeries, seriesCount, setSelectedSeriesId, setSeriesCount]);

// Reset series selection if query data changes
useEffect(() => {
Expand Down
131 changes: 131 additions & 0 deletions src/components/Utilities/ReleaseManagement/QuickSelectModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import React, { useEffect } from 'react';
import { map, toNumber } from 'lodash';
import { useImmer } from 'use-immer';

import Button from '@/components/Input/Button';
import Checkbox from '@/components/Input/Checkbox';
import ModalPanel from '@/components/Panels/ModalPanel';
import toast from '@/components/Toast';
import { useDeleteFilesMutation } from '@/core/react-query/file/mutations';
import { useSeriesFileSummaryQuery } from '@/core/react-query/webui/queries';
import useEventCallback from '@/hooks/useEventCallback';

type Props = {
show: boolean;
onClose: () => void;
seriesId: number;
};

const QuickSelectModal = ({ onClose, seriesId, show }: Props) => {
const fileSummaryQuery = useSeriesFileSummaryQuery(
seriesId,
{
groupBy: 'GroupName,FileSource,VideoResolution,AudioLanguages,SubtitleLanguages',
includeEpisodeDetails: true,
},
show,
);
const fileSummary = fileSummaryQuery.data;

const { isPending: isDeleting, mutate: deleteFiles } = useDeleteFilesMutation();

const [groupsToDelete, setGroupsToDelete] = useImmer<Set<number>>(new Set());

useEffect(() => {
setGroupsToDelete(new Set());
}, [setGroupsToDelete, show]);

const handleCheckboxChange = useEventCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const index = toNumber(event.target.id.split('-')[1]);
setGroupsToDelete((state) => {
if (event.target.checked) state.add(index);
else state.delete(index);
});
});

const handleSave = useEventCallback(() => {
const fileIds = map(
[...groupsToDelete],
groupIndex =>
map(
fileSummary?.Groups[groupIndex].Episodes,
episode => episode.FileID,
),
).flat();

deleteFiles(
{ fileIds, removeFolder: true },
{
onSuccess: () => {
toast.success(`${fileIds.length} ${fileIds.length === 1 ? 'file' : 'files'} deleted!`);
onClose();
},
onError: () => toast.error('Files could not be deleted!'),
},
);
});

return (
<ModalPanel show={show} onRequestClose={onClose} header="Quick Select" size="sm">
{fileSummaryQuery.isSuccess && (
map(
fileSummary?.Groups,
(group, index) => (
<div key={`group-${index}`} className="flex items-center justify-between gap-x-3">
<div className="flex flex-col gap-y-2">
<div className="font-semibold">{group.GroupName}</div>
<div className="flex opacity-65">
{group.FileSource}
&nbsp;|&nbsp;
{group.VideoResolution}
{group.AudioLanguages && (
<>
&nbsp;|&nbsp;
<div>
{group.AudioLanguages.length > 1 ? 'Multi ' : 'Single '}
Audio (
{group.AudioLanguages.join(', ')}
)
</div>
</>
)}
{group.SubtitleLanguages && (
<>
&nbsp;|&nbsp;
<div>
{group.SubtitleLanguages.length ? 'Multi ' : 'Single '}
Subs (
{group.SubtitleLanguages.join(', ')}
)
</div>
</>
)}
</div>
</div>
<Checkbox
id={`checkbox-${index}`}
isChecked={groupsToDelete.has(index)}
onChange={handleCheckboxChange}
label="Delete"
/>
</div>
),
)
)}

<div className="mt-4 flex justify-end gap-x-3 font-semibold">
<Button onClick={onClose} buttonType="secondary" className="px-6 py-2">Cancel</Button>
<Button
onClick={handleSave}
buttonType="primary"
className="px-6 py-2"
loading={isDeleting}
>
Save
</Button>
</div>
</ModalPanel>
);
};

export default QuickSelectModal;
1 change: 1 addition & 0 deletions src/core/react-query/webui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type GroupViewRequestType = {

export type SeriesFileSummaryRequestType = {
groupBy?: string;
includeEpisodeDetails?: boolean;
};

export type WebuiUpdateCheckRequestType = {
Expand Down
6 changes: 6 additions & 0 deletions src/core/signalr/eventHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ const invalidateImportFolders = debounce(
// Should we add a debounce here? It seems to work fine without it
const invalidateQueueItems = () => invalidateQueries(['queue', 'items']);

const invalidateReleaseManagement = debounce(
() => invalidateQueries(['release-management']),
5000,
);

const invalidateSeries = debounce(
(seriesId: number, groupIds: number[]) => {
invalidateQueries(['series', seriesId]);
Expand All @@ -39,6 +44,7 @@ export const handleEvent = (event: string, data?: SeriesUpdateEventType) => {
invalidateDashboard();
invalidateFiles();
invalidateImportFolders();
invalidateReleaseManagement();
break;
case 'FileMoved':
invalidateFiles();
Expand Down
9 changes: 9 additions & 0 deletions src/core/types/api/webui.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { CollectionFilterType } from './collection';
import type { ImageType, RatingType } from './common';
import type { SeriesTitleType } from './series';
import type { EpisodeTypeEnum } from '@/core/types/api/episode';
import type { TagType } from '@/core/types/api/tags';

export type WebuiGroupExtra = {
Expand Down Expand Up @@ -50,6 +51,14 @@ export type WebuiSeriesFileSummaryGroupType = {
SubtitleLanguages?: string[];
SubtitleStreamCount?: number;
RangeByType: WebuiSeriesFileSummaryGroupRangeByType;
Episodes?: {
ED2K: string;
EpisodeID: number;
FileID: number;
Number: number;
Size: number;
Type: EpisodeTypeEnum;
}[];
};

export type WebuiSeriesFileSummaryGroupRangeByType = {
Expand Down
4 changes: 4 additions & 0 deletions src/core/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import advancedFormat from 'dayjs/plugin/advancedFormat';
import calendar from 'dayjs/plugin/calendar';
import durationPlugin from 'dayjs/plugin/duration';
import formatThousands from 'format-thousands';
import { enableMapSet } from 'immer';
import { isObject, toNumber } from 'lodash';

dayjs.extend(advancedFormat);
Expand All @@ -12,6 +13,9 @@ dayjs.extend(durationPlugin);

export { default as dayjs } from 'dayjs';

// Enables immer plugin to support Map and Set
enableMapSet();

const { DEV, VITE_APPVERSION, VITE_GITHASH } = import.meta.env;

export function uiVersion() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { mdiCloseCircleOutline, mdiFileDocumentMultipleOutline, mdiRefresh } from '@mdi/js';
import { mdiCloseCircleOutline, mdiFileDocumentMultipleOutline, mdiRefresh, mdiSelectMultiple } from '@mdi/js';
import { Icon } from '@mdi/react';
import cx from 'classnames';
import { map, toNumber } from 'lodash';
Expand All @@ -13,6 +13,7 @@ import TransitionDiv from '@/components/TransitionDiv';
import ItemCount from '@/components/Utilities/ItemCount';
import MultiplesUtilEpisode from '@/components/Utilities/ReleaseManagement/MultiplesUtilEpisode';
import MultiplesUtilList from '@/components/Utilities/ReleaseManagement/MultiplesUtilList';
import QuickSelectModal from '@/components/Utilities/ReleaseManagement/QuickSelectModal';
import Title from '@/components/Utilities/ReleaseManagement/Title';
import MenuButton from '@/components/Utilities/Unrecognized/MenuButton';
import { useDeleteFileMutation, useMarkVariationMutation } from '@/core/react-query/file/mutations';
Expand All @@ -26,9 +27,11 @@ const MultiplesUtil = () => {
const [ignoreVariations, toggleIgnoreVariations] = useToggle(true);
const [onlyFinishedSeries, toggleOnlyFinishedSeries] = useToggle(true);
const [seriesCount, setSeriesCount] = useState(0);
const [selectedSeries, setSelectedSeries] = useState(0);
const [selectedEpisode, setSelectedEpisode] = useState<EpisodeType>();
const [operationsPending, setOperationsPending] = useState(false);
const [fileOptions, setFileOptions] = useState<MultipleFileOptionsType>({});
const [showQuickSelectModal, toggleShowQuickSelectModal] = useToggle(false);

const { mutateAsync: deleteFile } = useDeleteFileMutation();
const { mutateAsync: markVariation } = useMarkVariationMutation();
Expand Down Expand Up @@ -107,6 +110,16 @@ const MultiplesUtil = () => {
{/* </Button> */}
{/* )} */}

<Button
buttonType="secondary"
className="flex gap-x-2.5 px-4 py-3 font-semibold"
disabled={!selectedSeries}
onClick={toggleShowQuickSelectModal}
>
<Icon path={mdiSelectMultiple} size={0.8333} />
Quick Select
</Button>

{selectedEpisode && (
<div className="flex items-center justify-end gap-x-3">
<Button
Expand Down Expand Up @@ -137,6 +150,7 @@ const MultiplesUtil = () => {
ignoreVariations={ignoreVariations}
onlyFinishedSeries={onlyFinishedSeries}
setSelectedEpisode={setSelectedEpisode}
setSelectedSeriesId={setSelectedSeries}
setSeriesCount={setSeriesCount}
/>
</TransitionDiv>
Expand All @@ -151,6 +165,12 @@ const MultiplesUtil = () => {
/>
</TransitionDiv>
</div>

<QuickSelectModal
show={showQuickSelectModal}
onClose={toggleShowQuickSelectModal}
seriesId={selectedSeries}
/>
</div>
);
};
Expand Down

0 comments on commit e9f19f8

Please sign in to comment.