Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Fallenbagel
2023-02-28 03:28:36 +05:00
57 changed files with 1122 additions and 191 deletions

View File

@@ -10,6 +10,7 @@ import useSettings from '@app/hooks/useSettings';
import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import Error from '@app/pages/_error';
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
import { ArrowDownTrayIcon } from '@heroicons/react/24/outline';
import { MediaStatus } from '@server/constants/media';
import type { Collection } from '@server/models/Collection';
@@ -39,20 +40,8 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
const [requestModal, setRequestModal] = useState(false);
const [is4k, setIs4k] = useState(false);
const {
data,
error,
mutate: revalidate,
} = useSWR<Collection>(`/api/v1/collection/${router.query.collectionId}`, {
fallbackData: collection,
revalidateOnMount: true,
});
const { data: genres } =
useSWR<{ id: number; name: string }[]>(`/api/v1/genres/movie`);
const [downloadStatus, downloadStatus4k] = useMemo(() => {
return [
const returnCollectionDownloadItems = (data: Collection | undefined) => {
const [downloadStatus, downloadStatus4k] = [
data?.parts.flatMap((item) =>
item.mediaInfo?.downloadStatus ? item.mediaInfo?.downloadStatus : []
),
@@ -60,7 +49,30 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
item.mediaInfo?.downloadStatus4k ? item.mediaInfo?.downloadStatus4k : []
),
];
}, [data?.parts]);
return { downloadStatus, downloadStatus4k };
};
const {
data,
error,
mutate: revalidate,
} = useSWR<Collection>(`/api/v1/collection/${router.query.collectionId}`, {
fallbackData: collection,
revalidateOnMount: true,
refreshInterval: refreshIntervalHelper(
returnCollectionDownloadItems(collection),
15000
),
});
const { data: genres } =
useSWR<{ id: number; name: string }[]>(`/api/v1/genres/movie`);
const [downloadStatus, downloadStatus4k] = useMemo(() => {
const downloadItems = returnCollectionDownloadItems(data);
return [downloadItems.downloadStatus, downloadItems.downloadStatus4k];
}, [data]);
const [titles, titles4k] = useMemo(() => {
return [

View File

@@ -101,12 +101,12 @@ const ButtonWithDropdown = ({
<Transition
as={Fragment}
show={isOpen}
enter="transition ease-out duration-100 opacity-0"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75 opacity-100"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
enter="transition ease-out duration-100"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<div className="absolute right-0 z-40 mt-2 -mr-1 w-56 origin-top-right rounded-md shadow-lg">
<div

View File

@@ -78,10 +78,10 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
appear
as="div"
className="fixed top-0 bottom-0 left-0 right-0 z-50 flex h-full w-full items-center justify-center bg-gray-800 bg-opacity-70"
enter="transition opacity-0 duration-300"
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition opacity-100 duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
ref={parentRef}
@@ -89,10 +89,10 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
<Transition
appear
as={Fragment}
enter="transition opacity-0 duration-300 transform scale-75"
enter="transition duration-300"
enterFrom="opacity-0 scale-75"
enterTo="opacity-100 scale-100"
leave="transition opacity-100 duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={loading}
@@ -102,7 +102,7 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
</div>
</Transition>
<Transition
className="hide-scrollbar relative inline-block w-full transform overflow-auto bg-gray-800 px-4 pt-4 pb-4 text-left align-bottom shadow-xl ring-1 ring-gray-700 transition-all sm:my-8 sm:max-w-3xl sm:rounded-lg sm:align-middle"
className="hide-scrollbar relative inline-block w-full overflow-auto bg-gray-800 px-4 pt-4 pb-4 text-left align-bottom shadow-xl ring-1 ring-gray-700 transition-all sm:my-8 sm:max-w-3xl sm:rounded-lg sm:align-middle"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
@@ -111,10 +111,10 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
}}
appear
as="div"
enter="transition opacity-0 duration-300 transform scale-75"
enter="transition duration-300"
enterFrom="opacity-0 scale-75"
enterTo="opacity-100 scale-100"
leave="transition opacity-100 duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={!loading}

View File

@@ -29,7 +29,7 @@ const SlideCheckbox = ({ onClick, checked = false }: SlideCheckboxProps) => {
aria-hidden="true"
className={`${
checked ? 'translate-x-5' : 'translate-x-0'
} absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
} absolute left-0 inline-block h-5 w-5 rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
></span>
</span>
);

View File

@@ -37,10 +37,10 @@ const SlideOver = ({
as={Fragment}
show={show}
appear
enter="opacity-0 transition ease-in-out duration-300"
enter="transition-opacity ease-in-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition ease-in-out duration-300"
leave="transition-opacity ease-in-out duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
@@ -58,16 +58,16 @@ const SlideOver = ({
<section className="absolute inset-y-0 right-0 flex max-w-full">
<Transition.Child
appear
enter="transform transition ease-in-out duration-500 sm:duration-700"
enter="transition-transform ease-in-out duration-500 sm:duration-700"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transform transition ease-in-out duration-500 sm:duration-700"
leave="transition-transform ease-in-out duration-500 sm:duration-700"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div
className="slideover relative h-full w-screen max-w-md p-2 sm:p-4"
className="slideover relative h-full w-screen max-w-md p-2 sm:p-3"
ref={slideoverRef}
onClick={(e) => e.stopPropagation()}
>

View File

@@ -165,10 +165,10 @@ const Discover = () => {
</Transition>
<Transition
show={isEditing}
enter="transition transform duration-300"
enter="transition duration-300"
enterFrom="opacity-0 translate-y-6"
enterTo="opacity-100 translate-y-0"
leave="transition duration-300 transform"
leave="transition duration-300"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-6"
className="safe-shift-edit-menu fixed right-0 left-0 z-50 flex flex-col items-center justify-end space-x-0 space-y-2 border-t border-gray-700 bg-gray-800 bg-opacity-80 p-4 backdrop-blur sm:bottom-0 sm:flex-row sm:space-y-0 sm:space-x-3"

View File

@@ -65,10 +65,10 @@ const IssueComment = ({
>
<Transition
as={Fragment}
enter="transition opacity-0 duration-300"
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition opacity-100 duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={showDeleteModal}
@@ -115,11 +115,11 @@ const IssueComment = ({
as={Fragment}
show={open}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Menu.Items
static
@@ -164,7 +164,7 @@ const IssueComment = ({
</Menu>
)}
<div
className={`absolute top-3 z-10 h-3 w-3 rotate-45 transform bg-gray-800 shadow ring-1 ring-gray-500 ${
className={`absolute top-3 z-10 h-3 w-3 rotate-45 bg-gray-800 shadow ring-1 ring-gray-500 ${
isReversed ? '-left-1' : '-right-1'
}`}
/>

View File

@@ -57,11 +57,11 @@ const IssueDescription = ({
show={open}
as="div"
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Menu.Items
static

View File

@@ -187,10 +187,10 @@ const IssueDetails = () => {
<PageTitle title={[intl.formatMessage(messages.issuepagetitle), title]} />
<Transition
as="div"
enter="transition opacity-0 duration-300"
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition opacity-100 duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={showDeleteModal}

View File

@@ -12,10 +12,10 @@ interface IssueModalProps {
const IssueModal = ({ show, mediaType, onCancel, tmdbId }: IssueModalProps) => (
<Transition
as="div"
enter="transition opacity-0 duration-300"
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition opacity-100 duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={show}

View File

@@ -34,12 +34,12 @@ const LanguagePicker = () => {
<Transition
as="div"
show={isDropdownOpen}
enter="transition ease-out duration-100 opacity-0"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75 opacity-100"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
enter="transition ease-out duration-100"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<div
className="absolute right-0 mt-2 w-56 origin-top-right rounded-md shadow-lg"

View File

@@ -131,13 +131,13 @@ const MobileMenu = () => {
show={isOpen}
as="div"
ref={ref}
enter="transition transform duration-500"
enter="transition duration-500"
enterFrom="opacity-0 translate-y-0"
enterTo="opacity-100 -translate-y-full"
leave="transition duration-500 transform"
leave="transition duration-500"
leaveFrom="opacity-100 -translate-y-full"
leaveTo="opacity-0 translate-y-0"
className="absolute top-0 left-0 right-0 flex w-full -translate-y-full transform flex-col space-y-6 border-t border-gray-600 bg-gray-900 bg-opacity-90 px-6 py-6 font-semibold text-gray-100 backdrop-blur"
className="absolute top-0 left-0 right-0 flex w-full -translate-y-full flex-col space-y-6 border-t border-gray-600 bg-gray-900 bg-opacity-90 px-6 py-6 font-semibold text-gray-100 backdrop-blur"
>
{filteredLinks.map((link) => {
const isActive = router.pathname.match(link.activeRegExp);

View File

@@ -128,10 +128,10 @@ const Sidebar = ({ open, setClosed }: SidebarProps) => {
</Transition.Child>
<Transition.Child
as="div"
enter="transition ease-in-out duration-300 transform"
enter="transition-transform ease-in-out duration-300"
enterFrom="-translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-300 transform"
leave="transition-transform ease-in-out duration-300"
leaveFrom="translate-x-0"
leaveTo="-translate-x-full"
>

View File

@@ -63,11 +63,11 @@ const UserDropdown = () => {
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
appear
>
<Menu.Items className="absolute right-0 mt-2 w-72 origin-top-right rounded-md shadow-lg">

View File

@@ -100,10 +100,10 @@ const Login = () => {
<Transition
as="div"
show={!!error}
enter="opacity-0 transition duration-300"
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>

View File

@@ -1,6 +1,7 @@
import Button from '@app/components/Common/Button';
import ConfirmButton from '@app/components/Common/ConfirmButton';
import SlideOver from '@app/components/Common/SlideOver';
import Tooltip from '@app/components/Common/Tooltip';
import DownloadBlock from '@app/components/DownloadBlock';
import IssueBlock from '@app/components/IssueBlock';
import RequestBlock from '@app/components/RequestBlock';
@@ -197,20 +198,24 @@ const ManageSlideOver = ({
<div className="overflow-hidden rounded-md border border-gray-700 shadow">
<ul>
{data.mediaInfo?.downloadStatus?.map((status, index) => (
<li
<Tooltip
key={`dl-status-${status.externalId}-${index}`}
className="border-b border-gray-700 last:border-b-0"
content={status.title}
>
<DownloadBlock downloadItem={status} />
</li>
<li className="border-b border-gray-700 last:border-b-0">
<DownloadBlock downloadItem={status} />
</li>
</Tooltip>
))}
{data.mediaInfo?.downloadStatus4k?.map((status, index) => (
<li
<Tooltip
key={`dl-status-${status.externalId}-${index}`}
className="border-b border-gray-700 last:border-b-0"
content={status.title}
>
<DownloadBlock downloadItem={status} is4k />
</li>
<li className="border-b border-gray-700 last:border-b-0">
<DownloadBlock downloadItem={status} is4k />
</li>
</Tooltip>
))}
</ul>
</div>

View File

@@ -26,6 +26,7 @@ import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import Error from '@app/pages/_error';
import { sortCrewPriority } from '@app/utils/creditHelpers';
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
import {
ArrowRightCircleIcon,
CloudIcon,
@@ -116,6 +117,13 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
mutate: revalidate,
} = useSWR<MovieDetailsType>(`/api/v1/movie/${router.query.movieId}`, {
fallbackData: movie,
refreshInterval: refreshIntervalHelper(
{
downloadStatus: movie?.mediaInfo?.downloadStatus,
downloadStatus4k: movie?.mediaInfo?.downloadStatus4k,
},
15000
),
});
const { data: ratingData } = useSWR<RTRating>(

View File

@@ -122,7 +122,7 @@ const RegionSelector = ({
<Transition
show={open}
leave="transition ease-in duration-100"
leave="transition-opacity ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
className="absolute mt-1 w-full rounded-md bg-gray-800 shadow-lg"

View File

@@ -7,6 +7,7 @@ import StatusBadge from '@app/components/StatusBadge';
import useDeepLinks from '@app/hooks/useDeepLinks';
import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
import { withProperties } from '@app/utils/typeHelpers';
import {
ArrowPathIcon,
@@ -220,6 +221,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
request.type === 'movie'
? `/api/v1/movie/${request.media.tmdbId}`
: `/api/v1/tv/${request.media.tmdbId}`;
const { data: title, error } = useSWR<MovieDetails | TvDetails>(
inView ? `${url}` : null
);
@@ -229,6 +231,13 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
mutate: revalidate,
} = useSWR<MediaRequest>(`/api/v1/request/${request.id}`, {
fallbackData: request,
refreshInterval: refreshIntervalHelper(
{
downloadStatus: request.media.downloadStatus,
downloadStatus4k: request.media.downloadStatus4k,
},
15000
),
});
const { mediaUrl: plexUrl, mediaUrl4k: plexUrl4k } = useDeepLinks({

View File

@@ -7,6 +7,7 @@ import StatusBadge from '@app/components/StatusBadge';
import useDeepLinks from '@app/hooks/useDeepLinks';
import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
import {
ArrowPathIcon,
CheckIcon,
@@ -293,6 +294,13 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
`/api/v1/request/${request.id}`,
{
fallbackData: request,
refreshInterval: refreshIntervalHelper(
{
downloadStatus: request.media.downloadStatus,
downloadStatus4k: request.media.downloadStatus4k,
},
15000
),
}
);

View File

@@ -582,10 +582,10 @@ const AdvancedRequester = ({
<Transition
show={open}
enter="transition ease-in duration-300"
enter="transition-opacity ease-in duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition ease-in duration-100"
leave="transition-opacity ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
className="mt-1 w-full rounded-md border border-gray-700 bg-gray-800 shadow-lg"

View File

@@ -324,7 +324,7 @@ const CollectionRequestModal = ({
aria-hidden="true"
className={`${
isAllParts() ? 'translate-x-5' : 'translate-x-0'
} absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
} absolute left-0 inline-block h-5 w-5 rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
></span>
</span>
</th>
@@ -389,7 +389,7 @@ const CollectionRequestModal = ({
isSelectedPart(part.id)
? 'translate-x-5'
: 'translate-x-0'
} absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
} absolute left-0 inline-block h-5 w-5 rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
></span>
</span>
</td>

View File

@@ -540,7 +540,7 @@ const TvRequestModal = ({
aria-hidden="true"
className={`${
isAllSeasons() ? 'translate-x-5' : 'translate-x-0'
} absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
} absolute left-0 inline-block h-5 w-5 rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
></span>
</span>
</th>
@@ -631,7 +631,7 @@ const TvRequestModal = ({
isSelectedSeason(season.seasonNumber)
? 'translate-x-5'
: 'translate-x-0'
} absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
} absolute left-0 inline-block h-5 w-5 rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
></span>
</span>
</td>

View File

@@ -29,10 +29,10 @@ const RequestModal = ({
return (
<Transition
as="div"
enter="transition opacity-0 duration-300"
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition opacity-100 duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={show}

View File

@@ -32,7 +32,7 @@ const LibraryItem = ({ isEnabled, name, onToggle }: LibraryItemProps) => {
aria-hidden="true"
className={`${
isEnabled ? 'translate-x-5' : 'translate-x-0'
} relative inline-block h-5 w-5 transform rounded-full bg-white shadow transition duration-200 ease-in-out`}
} relative inline-block h-5 w-5 rounded-full bg-white shadow transition duration-200 ease-in-out`}
>
<span
className={`${

View File

@@ -214,10 +214,10 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => {
as="div"
appear
show
enter="transition ease-in-out duration-300 transform opacity-0"
enter="transition-opacity ease-in-out duration-300"
enterFrom="opacity-0"
enterTo="opacuty-100"
leave="transition ease-in-out duration-300 transform opacity-100"
enterTo="opacity-100"
leave="transition-opacity ease-in-out duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>

View File

@@ -63,10 +63,10 @@ const Release = ({ currentVersion, release, isLatest }: ReleaseProps) => {
<div className="flex w-full flex-col space-y-3 rounded-md bg-gray-800 px-4 py-2 shadow-md ring-1 ring-gray-700 sm:flex-row sm:space-y-0 sm:space-x-3">
<Transition
as={Fragment}
enter="opacity-0 transition duration-300"
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={isModalOpen}

View File

@@ -57,6 +57,7 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages({
'plex-watchlist-sync': 'Plex Watchlist Sync',
'jellyfin-recently-added-sync': 'Jellyfin Recently Added Scan',
'jellyfin-full-sync': 'Jellyfin Full Library Scan',
'availability-sync': 'Media Availability Sync',
'radarr-scan': 'Radarr Scan',
'sonarr-scan': 'Sonarr Scan',
'download-sync': 'Download Sync',
@@ -71,6 +72,8 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages({
'Every {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}',
editJobScheduleSelectorMinutes:
'Every {jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}',
editJobScheduleSelectorSeconds:
'Every {jobScheduleSeconds, plural, one {second} other {{jobScheduleSeconds} seconds}}',
imagecache: 'Image Cache',
imagecacheDescription:
'When enabled in settings, Overseerr will proxy and cache images from pre-configured external sources. Cached images are saved into your config folder. You can find the files in <code>{appDataPath}/cache/images</code>.',
@@ -82,7 +85,7 @@ interface Job {
id: JobId;
name: string;
type: 'process' | 'command';
interval: 'short' | 'long' | 'fixed';
interval: 'seconds' | 'minutes' | 'hours' | 'fixed';
cronSchedule: string;
nextExecutionTime: string;
running: boolean;
@@ -93,10 +96,11 @@ type JobModalState = {
job?: Job;
scheduleHours: number;
scheduleMinutes: number;
scheduleSeconds: number;
};
type JobModalAction =
| { type: 'set'; hours?: number; minutes?: number }
| { type: 'set'; hours?: number; minutes?: number; seconds?: number }
| {
type: 'close';
}
@@ -119,6 +123,7 @@ const jobModalReducer = (
job: action.job,
scheduleHours: 1,
scheduleMinutes: 5,
scheduleSeconds: 30,
};
case 'set':
@@ -126,6 +131,7 @@ const jobModalReducer = (
...state,
scheduleHours: action.hours ?? state.scheduleHours,
scheduleMinutes: action.minutes ?? state.scheduleMinutes,
scheduleSeconds: action.seconds ?? state.scheduleSeconds,
};
}
};
@@ -153,6 +159,7 @@ const SettingsJobs = () => {
isOpen: false,
scheduleHours: 1,
scheduleMinutes: 5,
scheduleSeconds: 30,
});
const [isSaving, setIsSaving] = useState(false);
const settings = useSettings();
@@ -205,9 +212,11 @@ const SettingsJobs = () => {
const jobScheduleCron = ['0', '0', '*', '*', '*', '*'];
try {
if (jobModalState.job?.interval === 'short') {
if (jobModalState.job?.interval === 'seconds') {
jobScheduleCron.splice(0, 2, `*/${jobModalState.scheduleSeconds}`, '*');
} else if (jobModalState.job?.interval === 'minutes') {
jobScheduleCron[1] = `*/${jobModalState.scheduleMinutes}`;
} else if (jobModalState.job?.interval === 'long') {
} else if (jobModalState.job?.interval === 'hours') {
jobScheduleCron[2] = `*/${jobModalState.scheduleHours}`;
} else {
// jobs with interval: fixed should not be editable
@@ -249,10 +258,10 @@ const SettingsJobs = () => {
/>
<Transition
as={Fragment}
enter="opacity-0 transition duration-300"
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={jobModalState.isOpen}
@@ -291,7 +300,30 @@ const SettingsJobs = () => {
{intl.formatMessage(messages.editJobSchedulePrompt)}
</label>
<div className="form-input-area">
{jobModalState.job?.interval === 'short' ? (
{jobModalState.job?.interval === 'seconds' ? (
<select
name="jobScheduleSeconds"
className="inline"
value={jobModalState.scheduleSeconds}
onChange={(e) =>
dispatch({
type: 'set',
seconds: Number(e.target.value),
})
}
>
{[30, 45, 60].map((v) => (
<option value={v} key={`jobScheduleSeconds-${v}`}>
{intl.formatMessage(
messages.editJobScheduleSelectorSeconds,
{
jobScheduleSeconds: v,
}
)}
</option>
))}
</select>
) : jobModalState.job?.interval === 'minutes' ? (
<select
name="jobScheduleMinutes"
className="inline"

View File

@@ -143,10 +143,10 @@ const SettingsLogs = () => {
/>
<Transition
as={Fragment}
enter="opacity-0 transition duration-300"
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
appear

View File

@@ -247,10 +247,10 @@ const SettingsServices = () => {
<Transition
as={Fragment}
show={deleteServerModal.open}
enter="transition ease-in-out duration-300 transform opacity-0"
enter="transition-opacity ease-in-out duration-300"
enterFrom="opacity-0"
enterTo="opacuty-100"
leave="transition ease-in-out duration-300 transform opacity-100"
enterTo="opacity-100"
leave="transition-opacity ease-in-out duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>

View File

@@ -223,10 +223,10 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
as="div"
appear
show
enter="transition ease-in-out duration-300 transform opacity-0"
enter="transition-opacity ease-in-out duration-300"
enterFrom="opacity-0"
enterTo="opacuty-100"
leave="transition ease-in-out duration-300 transform opacity-100"
enterTo="opacity-100"
leave="transition-opacity ease-in-out duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>

View File

@@ -44,10 +44,10 @@ const StatusChecker = () => {
return (
<Transition
as={Fragment}
enter="opacity-0 transition duration-300"
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
appear

View File

@@ -141,7 +141,7 @@ const TitleCard = ({
: intl.formatMessage(globalMessages.tvshow)}
</div>
</div>
{currentStatus && (
{currentStatus && currentStatus !== MediaStatus.UNKNOWN && (
<div className="pointer-events-none z-40 flex items-center">
<StatusBadgeMini
status={currentStatus}
@@ -154,10 +154,10 @@ const TitleCard = ({
<Transition
as={Fragment}
show={isUpdating}
enter="transition ease-in-out duration-300 transform opacity-0"
enter="transition-opacity ease-in-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition ease-in-out duration-300 transform opacity-100"
leave="transition-opacity ease-in-out duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
@@ -169,10 +169,10 @@ const TitleCard = ({
<Transition
as={Fragment}
show={!image || showDetail || showRequestModal}
enter="transition transform opacity-0"
enter="transition-opacity"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition transform opacity-100"
leave="transition-opacity"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>

View File

@@ -30,6 +30,7 @@ import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import Error from '@app/pages/_error';
import { sortCrewPriority } from '@app/utils/creditHelpers';
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
import { Disclosure, Transition } from '@headlessui/react';
import {
ArrowRightCircleIcon,
@@ -112,6 +113,13 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
mutate: revalidate,
} = useSWR<TvDetailsType>(`/api/v1/tv/${router.query.tvId}`, {
fallbackData: tv,
refreshInterval: refreshIntervalHelper(
{
downloadStatus: tv?.mediaInfo?.downloadStatus,
downloadStatus4k: tv?.mediaInfo?.downloadStatus4k,
},
15000
),
});
const { data: ratingData } = useSWR<RTRating>(
@@ -759,18 +767,18 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
)}
<ChevronDownIcon
className={`${
open ? 'rotate-180 transform' : ''
open ? 'rotate-180' : ''
} h-6 w-6 text-gray-500`}
/>
</Disclosure.Button>
<Transition
show={open}
enter="transition duration-100 ease-out"
enterFrom="transform opacity-0"
enterTo="transform opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform opacity-100"
leaveTo="transform opacity-0"
enter="transition-opacity duration-100 ease-out"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-75 ease-out"
leaveFrom="opacity-100"
leaveTo="opacity-0"
// Not sure why this transition is adding a margin without this here
style={{ margin: '0px' }}
>

View File

@@ -155,7 +155,7 @@ const PlexImportModal = ({ onCancel, onComplete }: PlexImportProps) => {
aria-hidden="true"
className={`${
isAllUsers() ? 'translate-x-5' : 'translate-x-0'
} absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
} absolute left-0 inline-block h-5 w-5 rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
></span>
</span>
</th>
@@ -194,7 +194,7 @@ const PlexImportModal = ({ onCancel, onComplete }: PlexImportProps) => {
isSelectedUser(user.id)
? 'translate-x-5'
: 'translate-x-0'
} absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
} absolute left-0 inline-block h-5 w-5 rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
></span>
</span>
</td>

View File

@@ -233,10 +233,10 @@ const UserList = () => {
<PageTitle title={intl.formatMessage(messages.users)} />
<Transition
as="div"
enter="opacity-0 transition duration-300"
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={deleteModal.isOpen}
@@ -262,10 +262,10 @@ const UserList = () => {
<Transition
as="div"
enter="opacity-0 transition duration-300"
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={createModal.isOpen}
@@ -445,10 +445,10 @@ const UserList = () => {
<Transition
as="div"
enter="opacity-0 transition duration-300"
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={showBulkEditModal}
@@ -466,10 +466,10 @@ const UserList = () => {
<Transition
as="div"
enter="opacity-0 transition duration-300"
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition duration-300"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={showImportModal}

View File

@@ -631,6 +631,7 @@
"components.Settings.SettingsAbout.totalrequests": "Total Requests",
"components.Settings.SettingsAbout.uptodate": "Up to Date",
"components.Settings.SettingsAbout.version": "Version",
"components.Settings.SettingsJobsCache.availability-sync": "Media Availability Sync",
"components.Settings.SettingsJobsCache.cache": "Cache",
"components.Settings.SettingsJobsCache.cacheDescription": "Jellyseerr caches requests to external API endpoints to optimize performance and avoid making unnecessary API calls.",
"components.Settings.SettingsJobsCache.cacheflushed": "{cachename} cache flushed.",
@@ -649,6 +650,7 @@
"components.Settings.SettingsJobsCache.editJobSchedulePrompt": "New Frequency",
"components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Every {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}",
"components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Every {jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}",
"components.Settings.SettingsJobsCache.editJobScheduleSelectorSeconds": "Every {jobScheduleSeconds, plural, one {second} other {{jobScheduleSeconds} seconds}}",
"components.Settings.SettingsJobsCache.flushcache": "Flush Cache",
"components.Settings.SettingsJobsCache.jelly-recently-added-scan": "Jellyfin Recently Added Scan",
"components.Settings.SettingsJobsCache.jellyfin-full-scan": "Jellyfin Full Library Scan",

View File

@@ -43,8 +43,8 @@
}
.slideover {
padding-top: calc(1rem + env(safe-area-inset-top)) !important;
padding-bottom: calc(1rem + env(safe-area-inset-top)) !important;
padding-top: calc(0.75rem + env(safe-area-inset-top)) !important;
padding-bottom: calc(0.75rem + env(safe-area-inset-top)) !important;
}
.sidebar-close-button {

View File

@@ -0,0 +1,18 @@
import type { DownloadingItem } from '@server/lib/downloadtracker';
export const refreshIntervalHelper = (
downloadItem: {
downloadStatus: DownloadingItem[] | undefined;
downloadStatus4k: DownloadingItem[] | undefined;
},
timer: number
) => {
if (
(downloadItem.downloadStatus ?? []).length > 0 ||
(downloadItem.downloadStatus4k ?? []).length > 0
) {
return timer;
} else {
return 0;
}
};