From 22bce0cd940452f35b3ff330ed64814122f3f54c Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 31 Jan 2024 01:19:35 +0300 Subject: [PATCH 01/90] Add shared itemDto type --- src/types/itemDto.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/types/itemDto.ts diff --git a/src/types/itemDto.ts b/src/types/itemDto.ts new file mode 100644 index 0000000000..37fc15fd8e --- /dev/null +++ b/src/types/itemDto.ts @@ -0,0 +1,26 @@ +import type { BaseItemDto, BaseItemKind, CollectionTypeOptions, RecordingStatus, SearchHint, SeriesTimerInfoDto, TimerInfoDto, UserItemDataDto, VirtualFolderInfo } from '@jellyfin/sdk/lib/generated-client'; + +type BaseItem = Omit; +type TimerInfo = Omit; +type SeriesTimerInfo = Omit; +type SearchHintItem = Omit; +type UserItem = Omit; +type VirtualFolder = Omit; + +export interface ItemDto extends BaseItem, TimerInfo, SeriesTimerInfo, SearchHintItem, UserItem, VirtualFolder { + 'ChannelId'?: string | null; + 'EndDate'?: string | null; + 'Id'?: string | null; + 'StartDate'?: string | null; + 'Type'?: BaseItemKind | string | null; + 'Status'?: RecordingStatus | string | null; + 'CollectionType'?: CollectionTypeOptions | string | null; + 'Artists'?: Array | null; + 'MediaType'?: string | null; + 'Name'?: string | null; + 'ItemId'?: string | null; +} + +export type NullableString = string | null | undefined; +export type NullableNumber = number | null | undefined; +export type NullableBoolean = boolean | null | undefined; From 090e2991cb20832ffab4e00bf2a65429b5dc5a88 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 31 Jan 2024 01:20:35 +0300 Subject: [PATCH 02/90] Add RefreshIndicator --- .../RefreshIndicator.tsx | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx diff --git a/src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx b/src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx new file mode 100644 index 0000000000..aabc709201 --- /dev/null +++ b/src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx @@ -0,0 +1,92 @@ +import React, { FC, useCallback, useEffect, useState } from 'react'; +import Events, { Event } from 'utils/events'; +import serverNotifications from 'scripts/serverNotifications'; +import classNames from 'classnames'; + +import CircularProgress, { + CircularProgressProps +} from '@mui/material/CircularProgress'; +import Typography from '@mui/material/Typography'; +import Box from '@mui/material/Box'; +import { toPercent } from 'utils/number'; +import { getCurrentDateTimeLocale } from 'scripts/globalize'; +import type { ItemDto } from 'types/itemDto'; + +function CircularProgressWithLabel( + props: CircularProgressProps & { value: number } +) { + return ( + + + + + {toPercent(props.value / 100, getCurrentDateTimeLocale())} + + + + ); +} + +interface RefreshIndicatorProps { + item: ItemDto; + className?: string; +} + +const RefreshIndicator: FC = ({ item, className }) => { + const [progress, setProgress] = useState(item.RefreshProgress || 0); + + const onRefreshProgress = useCallback((_e: Event, apiClient, info) => { + if (info.ItemId === item?.Id) { + setProgress(parseFloat(info.Progress)); + } + }, [item?.Id]); + + const unbindEvents = useCallback(() => { + Events.off(serverNotifications, 'RefreshProgress', onRefreshProgress); + }, [onRefreshProgress]); + + const bindEvents = useCallback(() => { + unbindEvents(); + + if (item?.Id) { + Events.on(serverNotifications, 'RefreshProgress', onRefreshProgress); + } + }, [item?.Id, onRefreshProgress, unbindEvents]); + + useEffect(() => { + bindEvents(); + + return () => { + unbindEvents(); + }; + }, [bindEvents, item.Id, unbindEvents]); + + const progressringClass = classNames( + 'progressring', + className, + { 'hide': !progress || progress >= 100 } + ); + + return ( +
+ +
+ ); +}; + +export default RefreshIndicator; From 2e90f669e5c1c5c709608c7a39911ca7d65cd0f1 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 31 Jan 2024 02:59:45 +0300 Subject: [PATCH 03/90] Migrate Indicator to react --- src/components/indicators/indicators.scss | 8 + src/components/indicators/useIndicator.tsx | 260 ++++++++++++++++++ .../emby-progressbar/AutoTimeProgressBar.tsx | 77 ++++++ src/types/progressOptions.ts | 8 + 4 files changed, 353 insertions(+) create mode 100644 src/components/indicators/useIndicator.tsx create mode 100644 src/elements/emby-progressbar/AutoTimeProgressBar.tsx create mode 100644 src/types/progressOptions.ts diff --git a/src/components/indicators/indicators.scss b/src/components/indicators/indicators.scss index 29137a5df5..6e99a1c3c9 100644 --- a/src/components/indicators/indicators.scss +++ b/src/components/indicators/indicators.scss @@ -5,6 +5,14 @@ height: 0.28em; } +.itemLinearProgress { + width: 100%; + position: absolute; + left: 0; + bottom: 0; + border-radius: 100px; +} + .itemProgressBarForeground { position: absolute; top: 0; diff --git a/src/components/indicators/useIndicator.tsx b/src/components/indicators/useIndicator.tsx new file mode 100644 index 0000000000..3015094b14 --- /dev/null +++ b/src/components/indicators/useIndicator.tsx @@ -0,0 +1,260 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { LocationType } from '@jellyfin/sdk/lib/generated-client'; +import React from 'react'; +import Box from '@mui/material/Box'; +import LinearProgress, { + linearProgressClasses +} from '@mui/material/LinearProgress'; +import FiberSmartRecordIcon from '@mui/icons-material/FiberSmartRecord'; +import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord'; +import CheckIcon from '@mui/icons-material/Check'; +import VideocamIcon from '@mui/icons-material/Videocam'; +import FolderIcon from '@mui/icons-material/Folder'; +import PhotoAlbumIcon from '@mui/icons-material/PhotoAlbum'; +import PhotoIcon from '@mui/icons-material/Photo'; +import classNames from 'classnames'; +import datetime from 'scripts/datetime'; +import itemHelper from 'components/itemHelper'; +import AutoTimeProgressBar from 'elements/emby-progressbar/AutoTimeProgressBar'; +import type { ItemDto, NullableString } from 'types/itemDto'; +import type { ProgressOptions } from 'types/progressOptions'; + +const TypeIcon = { + Video: , + Folder: , + PhotoAlbum: , + Photo: +}; + +const getTypeIcon = (itemType: NullableString) => { + return TypeIcon[itemType as keyof typeof TypeIcon]; +}; + +const enableProgressIndicator = ( + itemType: NullableString, + itemMediaType: NullableString +) => { + return ( + (itemMediaType === 'Video' && itemType !== BaseItemKind.TvChannel) + || itemType === BaseItemKind.AudioBook + || itemType === 'AudioPodcast' + ); +}; + +const enableAutoTimeProgressIndicator = ( + itemType: NullableString, + itemStartDate: NullableString, + itemEndDate: NullableString +) => { + return ( + (itemType === BaseItemKind.Program + || itemType === 'Timer' + || itemType === BaseItemKind.Recording) + && Boolean(itemStartDate) + && Boolean(itemEndDate) + ); +}; + +const enablePlayedIndicator = (item: ItemDto) => { + return itemHelper.canMarkPlayed(item); +}; + +const useIndicator = (item: ItemDto) => { + const getMediaSourceIndicator = () => { + const mediaSourceCount = item.MediaSourceCount ?? 0; + if (mediaSourceCount > 1) { + return mediaSourceCount; + } + + return null; + }; + + const getMissingIndicator = () => { + if ( + item.Type === BaseItemKind.Episode + && item.LocationType === LocationType.Virtual + ) { + if (item.PremiereDate) { + try { + const premiereDate = datetime + .parseISO8601Date(item.PremiereDate) + .getTime(); + if (premiereDate > new Date().getTime()) { + return Unaired; + } + } catch (err) { + console.error(err); + } + } + return Missing; + } + + return null; + }; + + const getTimerIndicator = (className?: string) => { + const indicatorIconClass = classNames('timerIndicator', className); + + let status; + + if (item.Type === 'SeriesTimer') { + return ; + } else if (item.TimerId || item.SeriesTimerId) { + status = item.Status || 'Cancelled'; + } else if (item.Type === 'Timer') { + status = item.Status; + } else { + return null; + } + + if (item.SeriesTimerId) { + return ( + + ); + } + + return ; + }; + + const getTypeIndicator = () => { + const icon = getTypeIcon(item.Type); + if (icon) { + return {icon}; + } + return null; + }; + + const getChildCountIndicator = () => { + const childCount = item.ChildCount ?? 0; + + if (childCount > 1) { + return ( + + {datetime.toLocaleString(item.ChildCount)} + + ); + } + + return null; + }; + + const getPlayedIndicator = () => { + if (enablePlayedIndicator(item)) { + const userData = item.UserData || {}; + if (userData.UnplayedItemCount) { + return ( + + {datetime.toLocaleString(userData.UnplayedItemCount)} + + ); + } + + if ( + (userData.PlayedPercentage + && userData.PlayedPercentage >= 100) + || userData.Played + ) { + return ( + + + + ); + } + } + + return null; + }; + + const getProgress = (pct: number, progressOptions?: ProgressOptions) => { + const progressBarClass = classNames( + 'itemLinearProgress', + progressOptions?.containerClass + ); + + return ( + + ); + }; + + const getProgressBar = (progressOptions?: ProgressOptions) => { + if ( + enableProgressIndicator(item.Type, item.MediaType) + && item.Type !== 'Recording' + ) { + const playedPercentage = progressOptions?.userData?.PlayedPercentage ? + progressOptions.userData.PlayedPercentage : + item?.UserData?.PlayedPercentage; + if (playedPercentage && playedPercentage < 100) { + return getProgress(playedPercentage); + } + } + + if ( + enableAutoTimeProgressIndicator( + item.Type, + item.StartDate, + item.EndDate + ) + ) { + let startDate = 0; + let endDate = 1; + + try { + startDate = datetime.parseISO8601Date(item.StartDate).getTime(); + endDate = datetime.parseISO8601Date(item.EndDate).getTime(); + } catch (err) { + console.error(err); + } + + const now = new Date().getTime(); + const total = endDate - startDate; + const pct = 100 * ((now - startDate) / total); + + if (pct > 0 && pct < 100) { + const isRecording = + item.Type === 'Timer' + || item.Type === BaseItemKind.Recording + || Boolean(item.TimerId); + return ( + + ); + } + } + + return null; + }; + + return { + getProgress, + getProgressBar, + getMediaSourceIndicator, + getMissingIndicator, + getTimerIndicator, + getTypeIndicator, + getChildCountIndicator, + getPlayedIndicator + }; +}; + +export default useIndicator; diff --git a/src/elements/emby-progressbar/AutoTimeProgressBar.tsx b/src/elements/emby-progressbar/AutoTimeProgressBar.tsx new file mode 100644 index 0000000000..05b4e6de4a --- /dev/null +++ b/src/elements/emby-progressbar/AutoTimeProgressBar.tsx @@ -0,0 +1,77 @@ +import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; +import { ProgressOptions } from 'types/progressOptions'; +import LinearProgress, { linearProgressClasses } from '@mui/material/LinearProgress'; +import classNames from 'classnames'; + +interface AutoTimeProgressBarProps { + pct: number; + starTtime: number; + endTtime: number; + isRecording: boolean; + dataAutoMode?: string; + progressOptions?: ProgressOptions; +} + +const AutoTimeProgressBar: FC = ({ + pct, + dataAutoMode, + isRecording, + starTtime, + endTtime, + progressOptions +}) => { + const [progress, setProgress] = useState(pct); + const timerRef = useRef | null>(null); + + const onAutoTimeProgress = useCallback(() => { + const start = parseInt(starTtime.toString(), 10); + const end = parseInt(endTtime.toString(), 10); + + const now = new Date().getTime(); + const total = end - start; + let percentage = 100 * ((now - start) / total); + + percentage = Math.min(100, percentage); + percentage = Math.max(0, percentage); + + setProgress(percentage); + }, [endTtime, starTtime]); + + useEffect(() => { + if (timerRef.current) { + clearInterval(timerRef.current); + } + + if (dataAutoMode === 'time') { + timerRef.current = setInterval(onAutoTimeProgress, 60000); + } + + return () => { + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + } + }; + }, [dataAutoMode, onAutoTimeProgress]); + + const progressBarClass = classNames( + 'itemLinearProgress', + progressOptions?.containerClass + ); + + return ( + + ); +}; + +export default AutoTimeProgressBar; diff --git a/src/types/progressOptions.ts b/src/types/progressOptions.ts new file mode 100644 index 0000000000..ae043f2066 --- /dev/null +++ b/src/types/progressOptions.ts @@ -0,0 +1,8 @@ +import { UserItemDataDto } from '@jellyfin/sdk/lib/generated-client'; + +export interface ProgressOptions { + containerClass: string, + type?: string | null, + userData?: UserItemDataDto, + mediaType?: string +} From c3b5d503131572f43d28c5b45b5cd76ddf14c450 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 31 Jan 2024 03:01:58 +0300 Subject: [PATCH 04/90] Convert mediainfo PrimaryMediaInfo to react --- src/components/mediainfo/CaptionMediaInfo.tsx | 25 + .../mediainfo/CriticRatingMediaInfo.tsx | 25 + src/components/mediainfo/EndsAt.tsx | 31 ++ src/components/mediainfo/MediaInfoItem.tsx | 27 + src/components/mediainfo/PrimaryMediaInfo.tsx | 103 ++++ src/components/mediainfo/StarIcons.tsx | 29 + .../mediainfo/usePrimaryMediaInfo.tsx | 522 ++++++++++++++++++ src/types/mediaInfoItem.ts | 4 + 8 files changed, 766 insertions(+) create mode 100644 src/components/mediainfo/CaptionMediaInfo.tsx create mode 100644 src/components/mediainfo/CriticRatingMediaInfo.tsx create mode 100644 src/components/mediainfo/EndsAt.tsx create mode 100644 src/components/mediainfo/MediaInfoItem.tsx create mode 100644 src/components/mediainfo/PrimaryMediaInfo.tsx create mode 100644 src/components/mediainfo/StarIcons.tsx create mode 100644 src/components/mediainfo/usePrimaryMediaInfo.tsx create mode 100644 src/types/mediaInfoItem.ts diff --git a/src/components/mediainfo/CaptionMediaInfo.tsx b/src/components/mediainfo/CaptionMediaInfo.tsx new file mode 100644 index 0000000000..58a6f49af5 --- /dev/null +++ b/src/components/mediainfo/CaptionMediaInfo.tsx @@ -0,0 +1,25 @@ +import React, { FC } from 'react'; +import classNames from 'classnames'; +import ClosedCaptionIcon from '@mui/icons-material/ClosedCaption'; +import Box from '@mui/material/Box'; + +interface CaptionMediaInfoProps { + className?: string; +} + +const CaptionMediaInfo: FC = ({ className }) => { + const cssClass = classNames( + 'mediaInfoItem', + 'mediaInfoText', + 'closedCaptionMediaInfoText', + className + ); + + return ( + + + + ); +}; + +export default CaptionMediaInfo; diff --git a/src/components/mediainfo/CriticRatingMediaInfo.tsx b/src/components/mediainfo/CriticRatingMediaInfo.tsx new file mode 100644 index 0000000000..080aef78fa --- /dev/null +++ b/src/components/mediainfo/CriticRatingMediaInfo.tsx @@ -0,0 +1,25 @@ +import React, { FC } from 'react'; +import classNames from 'classnames'; +import Box from '@mui/material/Box'; + +interface CriticRatingMediaInfoProps { + className?: string; + criticRating: number; +} + +const CriticRatingMediaInfo: FC = ({ + className, + criticRating +}) => { + const cssClass = classNames( + 'mediaInfoCriticRating', + 'mediaInfoItem', + criticRating >= 60 ? + 'mediaInfoCriticRatingFresh' : + 'mediaInfoCriticRatingRotten', + className + ); + return {criticRating}; +}; + +export default CriticRatingMediaInfo; diff --git a/src/components/mediainfo/EndsAt.tsx b/src/components/mediainfo/EndsAt.tsx new file mode 100644 index 0000000000..693f949f8b --- /dev/null +++ b/src/components/mediainfo/EndsAt.tsx @@ -0,0 +1,31 @@ +import React, { FC } from 'react'; +import classNames from 'classnames'; +import Box from '@mui/material/Box'; +import datetime from 'scripts/datetime'; +import globalize from 'scripts/globalize'; + +interface EndsAtProps { + className?: string; + runTimeTicks: number +} + +const EndsAt: FC = ({ runTimeTicks, className }) => { + const cssClass = classNames( + 'mediaInfoItem', + 'mediaInfoText', + 'endsAt', + className + ); + + const endTime = new Date().getTime() + (runTimeTicks / 10000); + const endDate = new Date(endTime); + const displayTime = datetime.getDisplayTime(endDate); + + return ( + + {globalize.translate('EndsAtValue', displayTime)} + + ); +}; + +export default EndsAt; diff --git a/src/components/mediainfo/MediaInfoItem.tsx b/src/components/mediainfo/MediaInfoItem.tsx new file mode 100644 index 0000000000..b832e02e45 --- /dev/null +++ b/src/components/mediainfo/MediaInfoItem.tsx @@ -0,0 +1,27 @@ +import React, { FC } from 'react'; +import Box from '@mui/material/Box'; +import classNames from 'classnames'; +import type { MiscInfo } from 'types/mediaInfoItem'; + +interface MediaInfoItemProps { + className?: string; + miscInfo?: MiscInfo ; + +} + +const MediaInfoItem: FC = ({ className, miscInfo }) => { + const cssClass = classNames( + 'mediaInfoItem', + 'mediaInfoText', + className, + miscInfo?.cssClass + ); + + return ( + + {miscInfo?.text} + + ); +}; + +export default MediaInfoItem; diff --git a/src/components/mediainfo/PrimaryMediaInfo.tsx b/src/components/mediainfo/PrimaryMediaInfo.tsx new file mode 100644 index 0000000000..90b640054a --- /dev/null +++ b/src/components/mediainfo/PrimaryMediaInfo.tsx @@ -0,0 +1,103 @@ +import React, { FC } from 'react'; +import classNames from 'classnames'; +import Box from '@mui/material/Box'; +import usePrimaryMediaInfo from './usePrimaryMediaInfo'; + +import MediaInfoItem from './MediaInfoItem'; +import StarIcons from './StarIcons'; +import CaptionMediaInfo from './CaptionMediaInfo'; +import CriticRatingMediaInfo from './CriticRatingMediaInfo'; +import EndsAt from './EndsAt'; +import type { ItemDto } from 'types/itemDto'; +import type { MiscInfo } from 'types/mediaInfoItem'; + +interface PrimaryMediaInfoProps { + className?: string; + item: ItemDto; + isYearEnabled?: boolean; + isContainerEnabled?: boolean; + isEpisodeTitleEnabled?: boolean; + isCriticRatingEnabled?: boolean; + isEndsAtEnabled?: boolean; + isOriginalAirDateEnabled?: boolean; + isRuntimeEnabled?: boolean; + isProgramIndicatorEnabled?: boolean; + isEpisodeTitleIndexNumberEnabled?: boolean; + isOfficialRatingEnabled?: boolean; + isStarRatingEnabled?: boolean; + isCaptionIndicatorEnabled?: boolean; + isMissingIndicatorEnabled?: boolean; + getMissingIndicator: () => React.JSX.Element | null +} + +const PrimaryMediaInfo: FC = ({ + className, + item, + isYearEnabled = false, + isContainerEnabled = false, + isEpisodeTitleEnabled = false, + isCriticRatingEnabled = false, + isEndsAtEnabled = false, + isOriginalAirDateEnabled = false, + isRuntimeEnabled = false, + isProgramIndicatorEnabled = false, + isEpisodeTitleIndexNumberEnabled = false, + isOfficialRatingEnabled = false, + isStarRatingEnabled = false, + isCaptionIndicatorEnabled = false, + isMissingIndicatorEnabled = false, + getMissingIndicator +}) => { + const miscInfo = usePrimaryMediaInfo({ + item, + isYearEnabled, + isContainerEnabled, + isEpisodeTitleEnabled, + isOriginalAirDateEnabled, + isRuntimeEnabled, + isProgramIndicatorEnabled, + isEpisodeTitleIndexNumberEnabled, + isOfficialRatingEnabled + }); + const { + StartDate, + HasSubtitles, + MediaType, + RunTimeTicks, + CommunityRating, + CriticRating + } = item; + + const cssClass = classNames(className); + + const renderMediaInfo = (info: MiscInfo | undefined, index: number) => ( + + ); + + return ( + + {miscInfo.map((info, index) => renderMediaInfo(info, index))} + + {isStarRatingEnabled && CommunityRating && ( + + )} + + {HasSubtitles && isCaptionIndicatorEnabled && } + + {CriticRating && isCriticRatingEnabled && ( + + )} + + {isEndsAtEnabled + && MediaType === 'Video' + && RunTimeTicks + && !StartDate && } + + {isMissingIndicatorEnabled && ( + getMissingIndicator() + )} + + ); +}; + +export default PrimaryMediaInfo; diff --git a/src/components/mediainfo/StarIcons.tsx b/src/components/mediainfo/StarIcons.tsx new file mode 100644 index 0000000000..d253a2db3d --- /dev/null +++ b/src/components/mediainfo/StarIcons.tsx @@ -0,0 +1,29 @@ +import React, { FC } from 'react'; +import classNames from 'classnames'; +import StarIcon from '@mui/icons-material/Star'; +import Box from '@mui/material/Box'; + +interface StarIconsProps { + className?: string; + communityRating: number; +} + +const StarIcons: FC = ({ className, communityRating }) => { + const cssClass = classNames( + 'mediaInfoItem', + 'mediaInfoText', + 'starRatingContainer', + className + ); + + return ( + + + {communityRating.toFixed(1)} + + ); +}; + +export default StarIcons; diff --git a/src/components/mediainfo/usePrimaryMediaInfo.tsx b/src/components/mediainfo/usePrimaryMediaInfo.tsx new file mode 100644 index 0000000000..480f31dbbc --- /dev/null +++ b/src/components/mediainfo/usePrimaryMediaInfo.tsx @@ -0,0 +1,522 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; +import * as userSettings from 'scripts/settings/userSettings'; +import datetime from 'scripts/datetime'; +import globalize from 'scripts/globalize'; +import itemHelper from '../itemHelper'; +import type { ItemDto, NullableNumber, NullableString } from 'types/itemDto'; +import type { MiscInfo } from 'types/mediaInfoItem'; + +function shouldShowFolderRuntime( + itemType: NullableString, + itemMediaType: NullableString +): boolean { + return ( + itemType === BaseItemKind.MusicAlbum + || itemMediaType === 'MusicArtist' + || itemType === BaseItemKind.Playlist + || itemMediaType === 'Playlist' + || itemMediaType === 'MusicGenre' + ); +} + +function addTrackCountOrItemCount( + showFolderRuntime: boolean, + itemSongCount: NullableNumber, + itemChildCount: NullableNumber, + itemRunTimeTicks: NullableNumber, + itemType: NullableString, + addMiscInfo: (val: MiscInfo) => void +): void { + if (showFolderRuntime) { + const count = itemSongCount ?? itemChildCount; + if (count) { + addMiscInfo({ text: globalize.translate('TrackCount', count) }); + } + + if (itemRunTimeTicks) { + addMiscInfo({ text: datetime.getDisplayDuration(itemRunTimeTicks) }); + } + } else if (itemType === BaseItemKind.PhotoAlbum || itemType === BaseItemKind.BoxSet) { + const count = itemChildCount; + if (count) { + addMiscInfo({ text: globalize.translate('ItemCount', count) }); + } + } +} + +function addOriginalAirDateInfo( + itemType: NullableString, + itemMediaType: NullableString, + isOriginalAirDateEnabled: boolean, + itemPremiereDate: NullableString, + addMiscInfo: (val: MiscInfo) => void +): void { + if ( + itemPremiereDate + && (itemType === BaseItemKind.Episode || itemMediaType === 'Photo') + && isOriginalAirDateEnabled + ) { + try { + //don't modify date to locale if episode. Only Dates (not times) are stored, or editable in the edit metadata dialog + const date = datetime.parseISO8601Date( + itemPremiereDate, + itemType !== BaseItemKind.Episode + ); + addMiscInfo({ text: datetime.toLocaleDateString(date) }); + } catch (e) { + console.error('error parsing date:', itemPremiereDate); + } + } +} + +function addSeriesTimerInfo( + itemType: NullableString, + itemRecordAnyTime: boolean | undefined, + itemStartDate: NullableString, + itemRecordAnyChannel: boolean | undefined, + itemChannelName: NullableString, + addMiscInfo: (val: MiscInfo) => void +): void { + if (itemType === 'SeriesTimer') { + if (itemRecordAnyTime) { + addMiscInfo({ text: globalize.translate('Anytime') }); + } else { + addMiscInfo({ text: datetime.getDisplayTime(itemStartDate) }); + } + + if (itemRecordAnyChannel) { + addMiscInfo({ text: globalize.translate('AllChannels') }); + } else { + addMiscInfo({ + text: itemChannelName ?? globalize.translate('OneChannel') + }); + } + } +} + +function addProgramIndicatorInfo( + program: ItemDto | undefined, + addMiscInfo: (val: MiscInfo) => void +): void { + if ( + program?.IsLive + && userSettings.get('guide-indicator-live', false) === 'true' + ) { + addMiscInfo({ + text: globalize.translate('Live'), + cssClass: 'mediaInfoProgramAttribute liveTvProgram' + }); + } else if ( + program?.IsPremiere + && userSettings.get('guide-indicator-premiere', false) === 'true' + ) { + addMiscInfo({ + text: globalize.translate('Premiere'), + cssClass: 'mediaInfoProgramAttribute premiereTvProgram' + }); + } else if ( + program?.IsSeries + && !program?.IsRepeat + && userSettings.get('guide-indicator-new', false) === 'true' + ) { + addMiscInfo({ + text: globalize.translate('New'), + cssClass: 'mediaInfoProgramAttribute newTvProgram' + }); + } else if ( + program?.IsSeries + && program?.IsRepeat + && userSettings.get('guide-indicator-repeat', false) === 'true' + ) { + addMiscInfo({ + text: globalize.translate('Repeat'), + cssClass: 'mediaInfoProgramAttribute repeatTvProgram' + }); + } +} + +function addProgramIndicators( + item: ItemDto, + isYearEnabled: boolean, + isEpisodeTitleEnabled: boolean, + isOriginalAirDateEnabled: boolean, + isProgramIndicatorEnabled: boolean, + isEpisodeTitleIndexNumberEnabled: boolean, + addMiscInfo: (val: MiscInfo) => void +): void { + if (item.Type === BaseItemKind.Program || item.Type === 'Timer') { + let program = item; + if (item.Type === 'Timer' && item.ProgramInfo) { + program = item.ProgramInfo; + } + + if (isProgramIndicatorEnabled !== false) { + addProgramIndicatorInfo(program, addMiscInfo); + } + + addProgramTextInfo( + program, + isEpisodeTitleEnabled, + isEpisodeTitleIndexNumberEnabled, + isOriginalAirDateEnabled, + isYearEnabled, + addMiscInfo + ); + } +} + +function addProgramTextInfo( + program: ItemDto, + isEpisodeTitleEnabled: boolean, + isEpisodeTitleIndexNumberEnabled: boolean, + isOriginalAirDateEnabled: boolean, + isYearEnabled: boolean, + addMiscInfo: (val: MiscInfo) => void +): void { + if ((program?.IsSeries || program?.EpisodeTitle) + && isEpisodeTitleEnabled !== false) { + const text = itemHelper.getDisplayName(program, { + includeIndexNumber: isEpisodeTitleIndexNumberEnabled + }); + + if (text) { + addMiscInfo({ text: text }); + } + } else if ( + program?.ProductionYear + && ((program?.IsMovie && isOriginalAirDateEnabled !== false) + || isYearEnabled !== false) + ) { + addMiscInfo({ text: program.ProductionYear }); + } else if (program?.PremiereDate && isOriginalAirDateEnabled !== false) { + try { + const date = datetime.parseISO8601Date(program.PremiereDate); + const text = globalize.translate( + 'OriginalAirDateValue', + datetime.toLocaleDateString(date) + ); + addMiscInfo({ text: text }); + } catch (e) { + console.error('error parsing date:', program.PremiereDate); + } + } +} + +function addStartDateInfo( + itemStartDate: NullableString, + itemType: NullableString, + addMiscInfo: (val: MiscInfo) => void +): void { + if ( + itemStartDate + && itemType !== BaseItemKind.Program + && itemType !== 'SeriesTimer' + && itemType !== 'Timer' + ) { + try { + const date = datetime.parseISO8601Date(itemStartDate); + addMiscInfo({ text: datetime.toLocaleDateString(date) }); + + if (itemType !== BaseItemKind.Recording) { + addMiscInfo({ text: datetime.getDisplayTime(date) }); + } + } catch (e) { + console.error('error parsing date:', itemStartDate); + } + } +} + +function addSeriesProductionYearInfo( + itemProductionYear: NullableNumber, + itemType: NullableString, + isYearEnabled: boolean, + itemStatus: NullableString, + itemEndDate: NullableString, + addMiscInfo: (val: MiscInfo) => void +): void { + if (itemProductionYear && isYearEnabled && itemType === BaseItemKind.Series) { + if (itemStatus === 'Continuing') { + addMiscInfo({ + text: globalize.translate( + 'SeriesYearToPresent', + datetime.toLocaleString(itemProductionYear, { + useGrouping: false + }) + ) + }); + } else { + addproductionYearWithEndDate(itemProductionYear, itemEndDate, addMiscInfo); + } + } +} + +function addproductionYearWithEndDate( + itemProductionYear: number, + itemEndDate: NullableString, + addMiscInfo: (val: MiscInfo) => void +): void { + let productionYear = datetime.toLocaleString(itemProductionYear, { + useGrouping: false + }); + + if (itemEndDate) { + try { + const endYear = datetime.toLocaleString( + datetime.parseISO8601Date(itemEndDate).getFullYear(), + { useGrouping: false } + ); + /* At this point, text will contain only the start year */ + if (endYear !== itemProductionYear) { + productionYear += `-${endYear}`; + } + } catch (e) { + console.error('error parsing date:', itemEndDate); + } + } + addMiscInfo({ text: productionYear }); +} + +function addYearInfo( + isYearEnabled: boolean, + itemType: NullableString, + itemMediaType: NullableString, + itemProductionYear: NullableNumber, + itemPremiereDate: NullableString, + addMiscInfo: (val: MiscInfo) => void +): void { + if ( + isYearEnabled + && itemType !== BaseItemKind.Series + && itemType !== BaseItemKind.Episode + && itemType !== BaseItemKind.Person + && itemMediaType !== 'Photo' + && itemType !== BaseItemKind.Program + && itemType !== BaseItemKind.Season + ) { + if (itemProductionYear) { + addMiscInfo({ text: itemProductionYear }); + } else if (itemPremiereDate) { + try { + const text = datetime.toLocaleString( + datetime.parseISO8601Date(itemPremiereDate).getFullYear(), + { useGrouping: false } + ); + addMiscInfo({ text: text }); + } catch (e) { + console.error('error parsing date:', itemPremiereDate); + } + } + } +} + +function addVideo3DFormat( + itemVideo3DFormat: NullableString, + addMiscInfo: (val: MiscInfo) => void +): void { + if (itemVideo3DFormat) { + addMiscInfo({ text: '3D' }); + } +} + +function addRunTimeInfo( + itemRunTimeTicks: NullableNumber, + itemType: NullableString, + showFolderRuntime: boolean, + isRuntimeEnabled: boolean, + addMiscInfo: (val: MiscInfo) => void +): void { + if ( + itemRunTimeTicks + && itemType !== BaseItemKind.Series + && itemType !== BaseItemKind.Program + && itemType !== 'Timer' + && itemType !== BaseItemKind.Book + && !showFolderRuntime + && isRuntimeEnabled + ) { + if (itemType === BaseItemKind.Audio) { + addMiscInfo({ text: datetime.getDisplayRunningTime(itemRunTimeTicks) }); + } else { + addMiscInfo({ text: datetime.getDisplayDuration(itemRunTimeTicks) }); + } + } +} + +function addOfficialRatingInfo( + itemOfficialRating: NullableString, + itemType: NullableString, + isOfficialRatingEnabled: boolean, + addMiscInfo: (val: MiscInfo) => void +): void { + if ( + itemOfficialRating + && isOfficialRatingEnabled + && itemType !== BaseItemKind.Season + && itemType !== BaseItemKind.Episode + ) { + addMiscInfo({ + text: itemOfficialRating, + cssClass: 'mediaInfoOfficialRating' + }); + } +} + +function addAudioContainer( + itemContainer: NullableString, + isContainerEnabled: boolean, + itemType: NullableString, + addMiscInfo: (val: MiscInfo) => void +): void { + if (itemContainer && isContainerEnabled && itemType === BaseItemKind.Audio) { + addMiscInfo({ text: itemContainer }); + } +} + +function addPhotoSize( + itemMediaType: NullableString, + itemWidth: NullableNumber, + itemHeight: NullableNumber, + addMiscInfo: (val: MiscInfo) => void +): void { + if (itemMediaType === 'Photo' && itemWidth && itemHeight) { + const size = `${itemWidth}x${itemHeight}`; + + addMiscInfo({ text: size }); + } +} + +interface UsePrimaryMediaInfoProps { + item: ItemDto; + isYearEnabled: boolean; + isContainerEnabled: boolean; + isEpisodeTitleEnabled: boolean; + isOriginalAirDateEnabled: boolean; + isRuntimeEnabled: boolean; + isProgramIndicatorEnabled: boolean; + isEpisodeTitleIndexNumberEnabled: boolean; + isOfficialRatingEnabled: boolean; +} + +function usePrimaryMediaInfo({ + item, + isYearEnabled = false, + isContainerEnabled = false, + isEpisodeTitleEnabled = false, + isOriginalAirDateEnabled = false, + isRuntimeEnabled = false, + isProgramIndicatorEnabled = false, + isEpisodeTitleIndexNumberEnabled = false, + isOfficialRatingEnabled = false +}: UsePrimaryMediaInfoProps) { + const { + EndDate, + Status, + StartDate, + ProductionYear, + Video3DFormat, + Type, + Width, + Height, + MediaType, + SongCount, + RecordAnyTime, + RecordAnyChannel, + ChannelName, + ChildCount, + RunTimeTicks, + PremiereDate, + OfficialRating, + Container + } = item; + + const miscInfo: MiscInfo[] = []; + + const addMiscInfo = (val: MiscInfo) => { + if (val) { + miscInfo.push(val); + } + }; + + const showFolderRuntime = shouldShowFolderRuntime(Type, MediaType); + + addTrackCountOrItemCount( + showFolderRuntime, + SongCount, + ChildCount, + RunTimeTicks, + Type, + addMiscInfo + ); + + addOriginalAirDateInfo( + Type, + MediaType, + isOriginalAirDateEnabled, + PremiereDate, + addMiscInfo + ); + + addSeriesTimerInfo( + Type, + RecordAnyTime, + StartDate, + RecordAnyChannel, + ChannelName, + addMiscInfo + ); + + addStartDateInfo(StartDate, Type, addMiscInfo); + + addSeriesProductionYearInfo( + ProductionYear, + Type, + isYearEnabled, + Status, + EndDate, + addMiscInfo + ); + + addProgramIndicators( + item, + isProgramIndicatorEnabled, + isEpisodeTitleEnabled, + isEpisodeTitleIndexNumberEnabled, + isOriginalAirDateEnabled, + isYearEnabled, + addMiscInfo + ); + + addYearInfo( + isYearEnabled, + Type, + MediaType, + ProductionYear, + PremiereDate, + addMiscInfo + ); + + addRunTimeInfo( + RunTimeTicks, + Type, + showFolderRuntime, + isRuntimeEnabled, + addMiscInfo + ); + + addOfficialRatingInfo( + OfficialRating, + Type, + isOfficialRatingEnabled, + addMiscInfo + ); + + addVideo3DFormat(Video3DFormat, addMiscInfo); + + addPhotoSize(MediaType, Width, Height, addMiscInfo); + + addAudioContainer(Container, isContainerEnabled, Type, addMiscInfo); + + return miscInfo; +} + +export default usePrimaryMediaInfo; diff --git a/src/types/mediaInfoItem.ts b/src/types/mediaInfoItem.ts new file mode 100644 index 0000000000..3620fad620 --- /dev/null +++ b/src/types/mediaInfoItem.ts @@ -0,0 +1,4 @@ +export interface MiscInfo { + text?: string | number; + cssClass?: string; +} From cc87ba38593791e89dc39fc6deae256149c073a0 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 31 Jan 2024 04:18:12 +0300 Subject: [PATCH 05/90] Add reusable component --- package-lock.json | 74 ++++++++++++++++++- package.json | 3 + src/components/common/DefaultIconText.tsx | 56 ++++++++++++++ src/components/common/DefaultName.tsx | 23 ++++++ src/components/common/Image.tsx | 67 +++++++++++++++++ src/components/common/InfoIconButton.tsx | 22 ++++++ src/components/common/Media.tsx | 36 +++++++++ src/components/common/MoreVertIconButton.tsx | 23 ++++++ src/components/common/NoItemsMessage.tsx | 25 +++++++ src/components/common/PlayArrowIconButton.tsx | 25 +++++++ .../common/PlaylistAddIconButton.tsx | 22 ++++++ src/components/common/RightIconButtons.tsx | 24 ++++++ src/types/dataAttributes.ts | 48 ++++++++++++ src/utils/image.ts | 37 +++++++++- src/utils/items.ts | 30 ++++++++ webpack.common.js | 2 + 16 files changed, 512 insertions(+), 5 deletions(-) create mode 100644 src/components/common/DefaultIconText.tsx create mode 100644 src/components/common/DefaultName.tsx create mode 100644 src/components/common/Image.tsx create mode 100644 src/components/common/InfoIconButton.tsx create mode 100644 src/components/common/Media.tsx create mode 100644 src/components/common/MoreVertIconButton.tsx create mode 100644 src/components/common/NoItemsMessage.tsx create mode 100644 src/components/common/PlayArrowIconButton.tsx create mode 100644 src/components/common/PlaylistAddIconButton.tsx create mode 100644 src/components/common/RightIconButtons.tsx create mode 100644 src/types/dataAttributes.ts diff --git a/package-lock.json b/package-lock.json index 18853390b0..559b3e61d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@react-hook/resize-observer": "1.2.6", "@tanstack/react-query": "4.36.1", "@tanstack/react-query-devtools": "4.36.1", + "@types/react-lazy-load-image-component": "1.6.3", "abortcontroller-polyfill": "1.7.5", "blurhash": "2.0.5", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", @@ -52,7 +53,9 @@ "native-promise-only": "0.8.1", "pdfjs-dist": "3.11.174", "react": "17.0.2", + "react-blurhash": "0.3.0", "react-dom": "17.0.2", + "react-lazy-load-image-component": "1.6.0", "react-router-dom": "6.21.3", "resize-observer-polyfill": "1.5.1", "screenfull": "6.0.2", @@ -4705,6 +4708,15 @@ "@types/react": "^17" } }, + "node_modules/@types/react-lazy-load-image-component": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@types/react-lazy-load-image-component/-/react-lazy-load-image-component-1.6.3.tgz", + "integrity": "sha512-HsIsYz7yWWTh/bftdzGnijKD26JyofLRqM/RM80sxs7Gk13G83ew8R/ra2XzXuiZfjNEjAq/Va+NBHFF9ciwxA==", + "dependencies": { + "@types/react": "*", + "csstype": "^3.0.2" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.10", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", @@ -12671,8 +12683,7 @@ "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -12686,6 +12697,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -16202,6 +16218,15 @@ "node": ">=0.10.0" } }, + "node_modules/react-blurhash": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/react-blurhash/-/react-blurhash-0.3.0.tgz", + "integrity": "sha512-XlKr4Ns1iYFRnk6DkAblNbAwN/bTJvxTVoxMvmTcURdc5oLoXZwqAF9N3LZUh/HT+QFlq5n6IS6VsDGsviYAiQ==", + "peerDependencies": { + "blurhash": "^2.0.3", + "react": ">=15" + } + }, "node_modules/react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -16220,6 +16245,19 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-lazy-load-image-component": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/react-lazy-load-image-component/-/react-lazy-load-image-component-1.6.0.tgz", + "integrity": "sha512-8KFkDTgjh+0+PVbH+cx0AgxLGbdTsxWMnxXzU5HEUztqewk9ufQAu8cstjZhyvtMIPsdMcPZfA0WAa7HtjQbBQ==", + "dependencies": { + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1" + }, + "peerDependencies": { + "react": "^15.x.x || ^16.x.x || ^17.x.x || ^18.x.x", + "react-dom": "^15.x.x || ^16.x.x || ^17.x.x || ^18.x.x" + } + }, "node_modules/react-router": { "version": "6.21.3", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.3.tgz", @@ -25943,6 +25981,15 @@ "@types/react": "^17" } }, + "@types/react-lazy-load-image-component": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@types/react-lazy-load-image-component/-/react-lazy-load-image-component-1.6.3.tgz", + "integrity": "sha512-HsIsYz7yWWTh/bftdzGnijKD26JyofLRqM/RM80sxs7Gk13G83ew8R/ra2XzXuiZfjNEjAq/Va+NBHFF9ciwxA==", + "requires": { + "@types/react": "*", + "csstype": "^3.0.2" + } + }, "@types/react-transition-group": { "version": "4.4.10", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", @@ -31866,8 +31913,7 @@ "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, "lodash.memoize": { "version": "4.1.2", @@ -31881,6 +31927,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -34295,6 +34346,12 @@ "object-assign": "^4.1.1" } }, + "react-blurhash": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/react-blurhash/-/react-blurhash-0.3.0.tgz", + "integrity": "sha512-XlKr4Ns1iYFRnk6DkAblNbAwN/bTJvxTVoxMvmTcURdc5oLoXZwqAF9N3LZUh/HT+QFlq5n6IS6VsDGsviYAiQ==", + "requires": {} + }, "react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -34310,6 +34367,15 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-lazy-load-image-component": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/react-lazy-load-image-component/-/react-lazy-load-image-component-1.6.0.tgz", + "integrity": "sha512-8KFkDTgjh+0+PVbH+cx0AgxLGbdTsxWMnxXzU5HEUztqewk9ufQAu8cstjZhyvtMIPsdMcPZfA0WAa7HtjQbBQ==", + "requires": { + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1" + } + }, "react-router": { "version": "6.21.3", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.3.tgz", diff --git a/package.json b/package.json index 9975e3f49f..aa6830dc2f 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "@react-hook/resize-observer": "1.2.6", "@tanstack/react-query": "4.36.1", "@tanstack/react-query-devtools": "4.36.1", + "@types/react-lazy-load-image-component": "1.6.3", "abortcontroller-polyfill": "1.7.5", "blurhash": "2.0.5", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", @@ -113,7 +114,9 @@ "native-promise-only": "0.8.1", "pdfjs-dist": "3.11.174", "react": "17.0.2", + "react-blurhash": "0.3.0", "react-dom": "17.0.2", + "react-lazy-load-image-component": "1.6.0", "react-router-dom": "6.21.3", "resize-observer-polyfill": "1.5.1", "screenfull": "6.0.2", diff --git a/src/components/common/DefaultIconText.tsx b/src/components/common/DefaultIconText.tsx new file mode 100644 index 0000000000..41f0014cb0 --- /dev/null +++ b/src/components/common/DefaultIconText.tsx @@ -0,0 +1,56 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC } from 'react'; +import Icon from '@mui/material/Icon'; +import imageHelper from 'utils/image'; +import DefaultName from './DefaultName'; +import type { ItemDto } from 'types/itemDto'; + +interface DefaultIconTextProps { + item: ItemDto; + defaultCardImageIcon?: string; +} + +const DefaultIconText: FC = ({ + item, + defaultCardImageIcon +}) => { + if (item.CollectionType) { + return ( + + ); + } + + if (item.Type && !(item.Type === BaseItemKind.TvChannel || item.Type === BaseItemKind.Studio )) { + return ( + + ); + } + + if (defaultCardImageIcon) { + return ( + + ); + } + + return ; +}; + +export default DefaultIconText; diff --git a/src/components/common/DefaultName.tsx b/src/components/common/DefaultName.tsx new file mode 100644 index 0000000000..5946fe27b5 --- /dev/null +++ b/src/components/common/DefaultName.tsx @@ -0,0 +1,23 @@ +import React, { FC } from 'react'; +import Box from '@mui/material/Box'; +import escapeHTML from 'escape-html'; +import itemHelper from 'components/itemHelper'; +import { isUsingLiveTvNaming } from '../cardbuilder/cardBuilderUtils'; +import type { ItemDto } from 'types/itemDto'; + +interface DefaultNameProps { + item: ItemDto; +} + +const DefaultName: FC = ({ item }) => { + const defaultName = isUsingLiveTvNaming(item.Type) ? + item.Name : + itemHelper.getDisplayName(item); + return ( + + {escapeHTML(defaultName)} + + ); +}; + +export default DefaultName; diff --git a/src/components/common/Image.tsx b/src/components/common/Image.tsx new file mode 100644 index 0000000000..14df552660 --- /dev/null +++ b/src/components/common/Image.tsx @@ -0,0 +1,67 @@ +import React, { FC, useCallback, useState } from 'react'; +import { BlurhashCanvas } from 'react-blurhash'; +import { LazyLoadImage } from 'react-lazy-load-image-component'; + +const imageStyle: React.CSSProperties = { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + width: '100%', + height: '100%', + zIndex: 0 +}; + +interface ImageProps { + imgUrl: string; + blurhash?: string; + containImage: boolean; +} + +const Image: FC = ({ + imgUrl, + blurhash, + containImage +}) => { + const [isLoaded, setIsLoaded] = useState(false); + const [isLoadStarted, setIsLoadStarted] = useState(false); + const handleLoad = useCallback(() => { + setIsLoaded(true); + }, []); + + const handleLoadStarted = useCallback(() => { + setIsLoadStarted(true); + }, []); + + return ( +
+ {!isLoaded && isLoadStarted && blurhash && ( + + )} + + +
+ ); +}; + +export default Image; diff --git a/src/components/common/InfoIconButton.tsx b/src/components/common/InfoIconButton.tsx new file mode 100644 index 0000000000..69c602e327 --- /dev/null +++ b/src/components/common/InfoIconButton.tsx @@ -0,0 +1,22 @@ +import React, { FC } from 'react'; +import IconButton from '@mui/material/IconButton'; +import InfoIcon from '@mui/icons-material/Info'; +import globalize from 'scripts/globalize'; + +interface InfoIconButtonProps { + className?: string; +} + +const InfoIconButton: FC = ({ className }) => { + return ( + + + + ); +}; + +export default InfoIconButton; diff --git a/src/components/common/Media.tsx b/src/components/common/Media.tsx new file mode 100644 index 0000000000..170208416f --- /dev/null +++ b/src/components/common/Media.tsx @@ -0,0 +1,36 @@ +import { BaseItemKind, ImageType } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC } from 'react'; +import Image from './Image'; +import DefaultIconText from './DefaultIconText'; +import type { ItemDto } from 'types/itemDto'; + +interface MediaProps { + item: ItemDto; + imgUrl: string | undefined; + blurhash: string | undefined; + imageType?: ImageType + defaultCardImageIcon?: string +} + +const Media: FC = ({ + item, + imgUrl, + blurhash, + imageType, + defaultCardImageIcon +}) => { + return imgUrl ? ( + + ) : ( + + ); +}; + +export default Media; diff --git a/src/components/common/MoreVertIconButton.tsx b/src/components/common/MoreVertIconButton.tsx new file mode 100644 index 0000000000..231a2afed1 --- /dev/null +++ b/src/components/common/MoreVertIconButton.tsx @@ -0,0 +1,23 @@ +import React, { FC } from 'react'; +import IconButton from '@mui/material/IconButton'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import globalize from 'scripts/globalize'; + +interface MoreVertIconButtonProps { + className?: string; + iconClassName?: string; +} + +const MoreVertIconButton: FC = ({ className, iconClassName }) => { + return ( + + + + ); +}; + +export default MoreVertIconButton; diff --git a/src/components/common/NoItemsMessage.tsx b/src/components/common/NoItemsMessage.tsx new file mode 100644 index 0000000000..2c59b0ed6b --- /dev/null +++ b/src/components/common/NoItemsMessage.tsx @@ -0,0 +1,25 @@ +import React, { FC } from 'react'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import globalize from 'scripts/globalize'; + +interface NoItemsMessageProps { + noItemsMessage?: string; +} + +const NoItemsMessage: FC = ({ + noItemsMessage = 'MessageNoItemsAvailable' +}) => { + return ( + + + {globalize.translate('MessageNothingHere')} + + + {globalize.translate(noItemsMessage)} + + + ); +}; + +export default NoItemsMessage; diff --git a/src/components/common/PlayArrowIconButton.tsx b/src/components/common/PlayArrowIconButton.tsx new file mode 100644 index 0000000000..b64fd9bd05 --- /dev/null +++ b/src/components/common/PlayArrowIconButton.tsx @@ -0,0 +1,25 @@ +import React, { FC } from 'react'; +import IconButton from '@mui/material/IconButton'; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; +import globalize from 'scripts/globalize'; + +interface PlayArrowIconButtonProps { + className: string; + action: string; + title: string; + iconClassName?: string; +} + +const PlayArrowIconButton: FC = ({ className, action, title, iconClassName }) => { + return ( + + + + ); +}; + +export default PlayArrowIconButton; diff --git a/src/components/common/PlaylistAddIconButton.tsx b/src/components/common/PlaylistAddIconButton.tsx new file mode 100644 index 0000000000..19469e0fe3 --- /dev/null +++ b/src/components/common/PlaylistAddIconButton.tsx @@ -0,0 +1,22 @@ +import React, { FC } from 'react'; +import IconButton from '@mui/material/IconButton'; +import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd'; +import globalize from 'scripts/globalize'; + +interface PlaylistAddIconButtonProps { + className?: string; +} + +const PlaylistAddIconButton: FC = ({ className }) => { + return ( + + + + ); +}; + +export default PlaylistAddIconButton; diff --git a/src/components/common/RightIconButtons.tsx b/src/components/common/RightIconButtons.tsx new file mode 100644 index 0000000000..2787a1856c --- /dev/null +++ b/src/components/common/RightIconButtons.tsx @@ -0,0 +1,24 @@ +import React, { FC } from 'react'; +import IconButton from '@mui/material/IconButton'; + +interface RightIconButtonsProps { + className?: string; + id: string; + icon: string; + title: string; +} + +const RightIconButtons: FC = ({ className, id, title, icon }) => { + return ( + + {icon} + + ); +}; + +export default RightIconButtons; diff --git a/src/types/dataAttributes.ts b/src/types/dataAttributes.ts new file mode 100644 index 0000000000..6e91b125cd --- /dev/null +++ b/src/types/dataAttributes.ts @@ -0,0 +1,48 @@ +import type { CollectionType, UserItemDataDto } from '@jellyfin/sdk/lib/generated-client'; +import type { NullableBoolean, NullableNumber, NullableString } from './itemDto'; + +export type AttributesOpts = { + context?: CollectionType | undefined, + parentId?: NullableString, + collectionId?: NullableString, + playlistId?: NullableString, + prefix?: NullableString, + action?: NullableString, + itemServerId?: NullableString, + itemId?: NullableString, + itemTimerId?: NullableString, + itemSeriesTimerId?: NullableString, + itemChannelId?: NullableString, + itemPlaylistItemId?: NullableString, + itemType?: NullableString, + itemMediaType?: NullableString, + itemCollectionType?: NullableString, + itemIsFolder?: NullableBoolean, + itemPath?: NullableString, + itemStartDate?: NullableString, + itemEndDate?: NullableString, + itemUserData?: UserItemDataDto +}; + +export type DataAttributes = { + 'data-playlistitemid'?: NullableString; + 'data-timerid'?: NullableString; + 'data-seriestimerid'?: NullableString; + 'data-serverid'?: NullableString; + 'data-id'?: NullableString; + 'data-type'?: NullableString; + 'data-collectionid'?: NullableString; + 'data-playlistid'?: NullableString; + 'data-mediatype'?: NullableString; + 'data-channelid'?: NullableString; + 'data-path'?: NullableString; + 'data-collectiontype'?: NullableString; + 'data-context'?: NullableString; + 'data-parentid'?: NullableString; + 'data-startdate'?: NullableString; + 'data-enddate'?: NullableString; + 'data-prefix'?: NullableString; + 'data-action'?: NullableString; + 'data-positionticks'?: NullableNumber; + 'data-isfolder'?: NullableBoolean; +}; diff --git a/src/utils/image.ts b/src/utils/image.ts index 7420fad41c..3819f865df 100644 --- a/src/utils/image.ts +++ b/src/utils/image.ts @@ -1,3 +1,4 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; import type { DeviceInfo } from '@jellyfin/sdk/lib/generated-client/models/device-info'; import type { SessionInfo } from '@jellyfin/sdk/lib/generated-client/models/session-info'; @@ -103,7 +104,41 @@ export function getLibraryIcon(library: string | null | undefined) { } } +export function getItemTypeIcon(itemType: BaseItemKind | string) { + switch (itemType) { + case BaseItemKind.MusicAlbum: + return 'album'; + case BaseItemKind.MusicArtist: + case BaseItemKind.Person: + return 'person'; + case BaseItemKind.Audio: + return 'audiotrack'; + case BaseItemKind.Movie: + return 'movie'; + case BaseItemKind.Episode: + case BaseItemKind.Series: + return 'tv'; + case BaseItemKind.Program: + return 'live_tv'; + case BaseItemKind.Book: + return 'book'; + case BaseItemKind.Folder: + return 'folder'; + case BaseItemKind.BoxSet: + return 'collections'; + case BaseItemKind.Playlist: + return 'view_list'; + case BaseItemKind.Photo: + return 'photo'; + case BaseItemKind.PhotoAlbum: + return 'photo_album'; + default: + return 'folder'; + } +} + export default { getDeviceIcon, - getLibraryIcon + getLibraryIcon, + getItemTypeIcon }; diff --git a/src/utils/items.ts b/src/utils/items.ts index 752226db26..6a5dcd0985 100644 --- a/src/utils/items.ts +++ b/src/utils/items.ts @@ -3,8 +3,10 @@ import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type' import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; import * as userSettings from 'scripts/settings/userSettings'; +import layoutManager from 'components/layoutManager'; import { EpisodeFilter, FeatureFilters, LibraryViewSettings, ParentId, VideoBasicFilter, ViewMode } from '../types/library'; import { LibraryTab } from 'types/libraryTab'; +import type { AttributesOpts, DataAttributes } from 'types/dataAttributes'; export const getVideoBasicFilter = (libraryViewSettings: LibraryViewSettings) => { let isHd; @@ -164,3 +166,31 @@ export const getDefaultLibraryViewSettings = (viewType: LibraryTab): LibraryView StartIndex: 0 }; }; + +export function getDataAttributes( + opts: AttributesOpts +): DataAttributes { + return { + 'data-context': opts.context, + 'data-collectionid': opts.collectionId, + 'data-playlistid': opts.playlistId, + 'data-parentid': opts.parentId, + 'data-playlistitemid': opts.itemPlaylistItemId, + 'data-action': layoutManager.tv ? opts.action : null, + 'data-serverid': opts.itemServerId, + 'data-id': opts.itemId, + 'data-timerid': opts.itemTimerId, + 'data-seriestimerid': opts.itemSeriesTimerId, + 'data-channelid': opts.itemChannelId, + 'data-type': opts.itemType, + 'data-mediatype': opts.itemMediaType, + 'data-collectiontype': opts.itemCollectionType, + 'data-isfolder': opts.itemIsFolder, + 'data-path': opts.itemPath, + 'data-prefix': opts.prefix, + 'data-positionticks': opts.itemUserData?.PlaybackPositionTicks, + 'data-startdate': opts.itemStartDate?.toString(), + 'data-enddate': opts.itemEndDate?.toString() + }; +} + diff --git a/webpack.common.js b/webpack.common.js index dd85d03183..1c4587e87b 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -200,6 +200,8 @@ const config = { path.resolve(__dirname, 'node_modules/markdown-it'), path.resolve(__dirname, 'node_modules/mdurl'), path.resolve(__dirname, 'node_modules/punycode'), + path.resolve(__dirname, 'node_modules/react-blurhash'), + path.resolve(__dirname, 'node_modules/react-lazy-load-image-component'), path.resolve(__dirname, 'node_modules/react-router'), path.resolve(__dirname, 'node_modules/screenfull'), path.resolve(__dirname, 'node_modules/ssr-window'), From 9efc71fa3b2c8fea9fba98e8203b56b50fbccd4d Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 31 Jan 2024 04:20:42 +0300 Subject: [PATCH 06/90] Convert ListView to react --- src/components/listview/List/List.tsx | 32 ++++ src/components/listview/List/ListContent.tsx | 106 +++++++++++ .../listview/List/ListContentWrapper.tsx | 34 ++++ .../listview/List/ListGroupHeaderWrapper.tsx | 30 +++ .../listview/List/ListImageContainer.tsx | 103 +++++++++++ src/components/listview/List/ListItemBody.tsx | 65 +++++++ .../listview/List/ListTextWrapper.tsx | 30 +++ .../listview/List/ListViewUserDataButtons.tsx | 87 +++++++++ src/components/listview/List/ListWrapper.tsx | 49 +++++ src/components/listview/List/Lists.tsx | 57 ++++++ src/components/listview/List/listHelper.ts | 171 ++++++++++++++++++ src/components/listview/List/useList.ts | 77 ++++++++ .../listview/List/useListTextlines.tsx | 167 +++++++++++++++++ src/components/listview/listview.scss | 1 + 14 files changed, 1009 insertions(+) create mode 100644 src/components/listview/List/List.tsx create mode 100644 src/components/listview/List/ListContent.tsx create mode 100644 src/components/listview/List/ListContentWrapper.tsx create mode 100644 src/components/listview/List/ListGroupHeaderWrapper.tsx create mode 100644 src/components/listview/List/ListImageContainer.tsx create mode 100644 src/components/listview/List/ListItemBody.tsx create mode 100644 src/components/listview/List/ListTextWrapper.tsx create mode 100644 src/components/listview/List/ListViewUserDataButtons.tsx create mode 100644 src/components/listview/List/ListWrapper.tsx create mode 100644 src/components/listview/List/Lists.tsx create mode 100644 src/components/listview/List/listHelper.ts create mode 100644 src/components/listview/List/useList.ts create mode 100644 src/components/listview/List/useListTextlines.tsx diff --git a/src/components/listview/List/List.tsx b/src/components/listview/List/List.tsx new file mode 100644 index 0000000000..995c057526 --- /dev/null +++ b/src/components/listview/List/List.tsx @@ -0,0 +1,32 @@ +import React, { FC } from 'react'; +import useList from './useList'; +import ListContent from './ListContent'; +import ListWrapper from './ListWrapper'; +import type { ItemDto } from 'types/itemDto'; +import type { ListOptions } from 'types/listOptions'; +import '../../mediainfo/mediainfo.scss'; +import '../../guide/programs.scss'; + +interface ListProps { + index: number; + item: ItemDto; + listOptions?: ListOptions; +} + +const List: FC = ({ index, item, listOptions = {} }) => { + const { getListdWrapperProps, getListContentProps } = useList({ item, listOptions } ); + const listWrapperProps = getListdWrapperProps(); + const listContentProps = getListContentProps(); + + return ( + + + + ); +}; + +export default List; diff --git a/src/components/listview/List/ListContent.tsx b/src/components/listview/List/ListContent.tsx new file mode 100644 index 0000000000..045c003f73 --- /dev/null +++ b/src/components/listview/List/ListContent.tsx @@ -0,0 +1,106 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC } from 'react'; +import DragHandleIcon from '@mui/icons-material/DragHandle'; +import Box from '@mui/material/Box'; + +import useIndicator from 'components/indicators/useIndicator'; +import PrimaryMediaInfo from '../../mediainfo/PrimaryMediaInfo'; +import ListContentWrapper from './ListContentWrapper'; +import ListItemBody from './ListItemBody'; +import ListImageContainer from './ListImageContainer'; +import ListViewUserDataButtons from './ListViewUserDataButtons'; + +import type { ItemDto } from 'types/itemDto'; +import type { ListOptions } from 'types/listOptions'; + +interface ListContentProps { + item: ItemDto; + listOptions: ListOptions; + enableContentWrapper?: boolean; + enableOverview?: boolean; + enableSideMediaInfo?: boolean; + clickEntireItem?: boolean; + action?: string; + isLargeStyle: boolean; + downloadWidth?: number; +} + +const ListContent: FC = ({ + item, + listOptions, + enableContentWrapper, + enableOverview, + enableSideMediaInfo, + clickEntireItem, + action, + isLargeStyle, + downloadWidth +}) => { + const indicator = useIndicator(item); + return ( + + + {!clickEntireItem && listOptions.dragHandle && ( + + )} + + {listOptions.image !== false && ( + + )} + + {listOptions.showIndexNumberLeft && ( + + {item.IndexNumber ??  } + + )} + + + + {listOptions.mediaInfo !== false && enableSideMediaInfo && ( + + )} + + {listOptions.recordButton + && (item.Type === 'Timer' || item.Type === BaseItemKind.Program) && ( + indicator.getTimerIndicator('listItemAside') + )} + + {!clickEntireItem && ( + + )} + + ); +}; + +export default ListContent; diff --git a/src/components/listview/List/ListContentWrapper.tsx b/src/components/listview/List/ListContentWrapper.tsx new file mode 100644 index 0000000000..1b0678ad50 --- /dev/null +++ b/src/components/listview/List/ListContentWrapper.tsx @@ -0,0 +1,34 @@ +import React, { FC } from 'react'; +import Box from '@mui/material/Box'; + +interface ListContentWrapperProps { + itemOverview: string | null | undefined; + enableContentWrapper?: boolean; + enableOverview?: boolean; +} + +const ListContentWrapper: FC = ({ + itemOverview, + enableContentWrapper, + enableOverview, + children +}) => { + if (enableContentWrapper) { + return ( + <> + {children} + + {enableOverview && itemOverview && ( + + {itemOverview} + + )} + + ); + } else { + // eslint-disable-next-line react/jsx-no-useless-fragment + return <>{children}; + } +}; + +export default ListContentWrapper; diff --git a/src/components/listview/List/ListGroupHeaderWrapper.tsx b/src/components/listview/List/ListGroupHeaderWrapper.tsx new file mode 100644 index 0000000000..f2a131e324 --- /dev/null +++ b/src/components/listview/List/ListGroupHeaderWrapper.tsx @@ -0,0 +1,30 @@ +import React, { FC } from 'react'; +import Typography from '@mui/material/Typography'; + +interface ListGroupHeaderWrapperProps { + index?: number; +} + +const ListGroupHeaderWrapper: FC = ({ + index, + children +}) => { + if (index === 0) { + return ( + + {children} + + ); + } else { + return ( + + {children} + + ); + } +}; + +export default ListGroupHeaderWrapper; diff --git a/src/components/listview/List/ListImageContainer.tsx b/src/components/listview/List/ListImageContainer.tsx new file mode 100644 index 0000000000..fe77707750 --- /dev/null +++ b/src/components/listview/List/ListImageContainer.tsx @@ -0,0 +1,103 @@ +import React, { FC } from 'react'; +import classNames from 'classnames'; +import Box from '@mui/material/Box'; +import { useApi } from 'hooks/useApi'; +import useIndicator from '../../indicators/useIndicator'; +import layoutManager from '../../layoutManager'; +import { getDefaultBackgroundClass } from '../../cardbuilder/cardBuilderUtils'; +import { + canResume, + getChannelImageUrl, + getImageUrl +} from './listHelper'; + +import Media from 'components/common/Media'; +import PlayArrowIconButton from 'components/common/PlayArrowIconButton'; +import type { ItemDto } from 'types/itemDto'; +import type { ListOptions } from 'types/listOptions'; + +interface ListImageContainerProps { + item: ItemDto; + listOptions: ListOptions; + action?: string | null; + isLargeStyle: boolean; + clickEntireItem?: boolean; + downloadWidth?: number; +} + +const ListImageContainer: FC = ({ + item = {}, + listOptions, + action, + isLargeStyle, + clickEntireItem, + downloadWidth +}) => { + const { api } = useApi(); + const { getMediaSourceIndicator, getProgressBar, getPlayedIndicator } = useIndicator(item); + const imgInfo = listOptions.imageSource === 'channel' ? + getChannelImageUrl(item, api, downloadWidth) : + getImageUrl(item, api, downloadWidth); + + const defaultCardImageIcon = listOptions.defaultCardImageIcon; + const disableIndicators = listOptions.disableIndicators; + const imgUrl = imgInfo?.imgUrl; + const blurhash = imgInfo.blurhash; + + const imageClass = classNames( + 'listItemImage', + { 'listItemImage-large': isLargeStyle }, + { 'listItemImage-channel': listOptions.imageSource === 'channel' }, + { 'listItemImage-large-tv': isLargeStyle && layoutManager.tv }, + { itemAction: !clickEntireItem }, + { [getDefaultBackgroundClass(item.Name)]: !imgUrl } + ); + + const playOnImageClick = listOptions.imagePlayButton && !layoutManager.tv; + + const imageAction = playOnImageClick ? 'link' : action; + + const btnCssClass = + 'paper-icon-button-light listItemImageButton itemAction'; + + const mediaSourceIndicator = getMediaSourceIndicator(); + const playedIndicator = getPlayedIndicator(); + const progressBar = getProgressBar(); + const playbackPositionTicks = item?.UserData?.PlaybackPositionTicks; + + return ( + + + + + {disableIndicators !== true && mediaSourceIndicator} + + {playedIndicator && ( + + {playedIndicator} + + )} + + {playOnImageClick && ( + + )} + + {progressBar} + + ); +}; + +export default ListImageContainer; diff --git a/src/components/listview/List/ListItemBody.tsx b/src/components/listview/List/ListItemBody.tsx new file mode 100644 index 0000000000..7d033c4f5d --- /dev/null +++ b/src/components/listview/List/ListItemBody.tsx @@ -0,0 +1,65 @@ +import React, { FC } from 'react'; +import classNames from 'classnames'; +import Box from '@mui/material/Box'; +import useListTextlines from './useListTextlines'; +import PrimaryMediaInfo from '../../mediainfo/PrimaryMediaInfo'; + +import type { ItemDto } from 'types/itemDto'; +import type { ListOptions } from 'types/listOptions'; + +interface ListItemBodyProps { + item: ItemDto; + listOptions: ListOptions; + action?: string | null; + isLargeStyle?: boolean; + clickEntireItem?: boolean; + enableContentWrapper?: boolean; + enableOverview?: boolean; + enableSideMediaInfo?: boolean; + getMissingIndicator: () => React.JSX.Element | null +} + +const ListItemBody: FC = ({ + item = {}, + listOptions = {}, + action, + isLargeStyle, + clickEntireItem, + enableContentWrapper, + enableOverview, + enableSideMediaInfo, + getMissingIndicator +}) => { + const { listTextLines } = useListTextlines({ item, listOptions, isLargeStyle }); + const cssClass = classNames( + 'listItemBody', + { 'itemAction': !clickEntireItem }, + { 'listItemBody-noleftpadding': listOptions.image === false } + ); + + return ( + + + {listTextLines} + + {listOptions.mediaInfo !== false && !enableSideMediaInfo && ( + + )} + + {!enableContentWrapper && enableOverview && item.Overview && ( + + {item.Overview} + + )} + + ); +}; + +export default ListItemBody; diff --git a/src/components/listview/List/ListTextWrapper.tsx b/src/components/listview/List/ListTextWrapper.tsx new file mode 100644 index 0000000000..c2139742ae --- /dev/null +++ b/src/components/listview/List/ListTextWrapper.tsx @@ -0,0 +1,30 @@ +import React, { FC } from 'react'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; + +interface ListTextWrapperProps { + index?: number; + isLargeStyle?: boolean; +} + +const ListTextWrapper: FC = ({ + index, + isLargeStyle, + children +}) => { + if (index === 0) { + if (isLargeStyle) { + return ( + + {children} + + ); + } else { + return {children}; + } + } else { + return {children}; + } +}; + +export default ListTextWrapper; diff --git a/src/components/listview/List/ListViewUserDataButtons.tsx b/src/components/listview/List/ListViewUserDataButtons.tsx new file mode 100644 index 0000000000..f3ad43ed9e --- /dev/null +++ b/src/components/listview/List/ListViewUserDataButtons.tsx @@ -0,0 +1,87 @@ +import React, { FC } from 'react'; +import { Box } from '@mui/material'; +import itemHelper from '../../itemHelper'; +import PlayedButton from 'elements/emby-playstatebutton/PlayedButton'; +import FavoriteButton from 'elements/emby-ratingbutton/FavoriteButton'; +import PlaylistAddIconButton from '../../common/PlaylistAddIconButton'; +import InfoIconButton from '../../common/InfoIconButton'; +import RightIconButtons from '../../common/RightIconButtons'; +import MoreVertIconButton from '../../common/MoreVertIconButton'; + +import type { ItemDto } from 'types/itemDto'; +import type { ListOptions } from 'types/listOptions'; + +interface ListViewUserDataButtonsProps { + item: ItemDto; + listOptions: ListOptions; +} + +const ListViewUserDataButtons: FC = ({ + item = {}, + listOptions +}) => { + const { IsFavorite, Played } = item.UserData ?? {}; + + const renderRightButtons = () => { + return listOptions.rightButtons?.map((button, index) => ( + + )); + }; + + return ( + + {listOptions.addToListButton && ( + + + )} + {listOptions.infoButton && ( + + + ) } + + {listOptions.rightButtons && renderRightButtons()} + + {listOptions.enableUserDataButtons !== false && ( + <> + {itemHelper.canMarkPlayed(item) + && listOptions.enablePlayedButton !== false && ( + + )} + + {itemHelper.canRate(item) + && listOptions.enableRatingButton !== false && ( + + )} + + )} + + {listOptions.moreButton !== false && ( + + )} + + ); +}; + +export default ListViewUserDataButtons; diff --git a/src/components/listview/List/ListWrapper.tsx b/src/components/listview/List/ListWrapper.tsx new file mode 100644 index 0000000000..a6d4ab292e --- /dev/null +++ b/src/components/listview/List/ListWrapper.tsx @@ -0,0 +1,49 @@ +import classNames from 'classnames'; +import escapeHTML from 'escape-html'; +import React, { FC } from 'react'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import layoutManager from '../../layoutManager'; +import type { DataAttributes } from 'types/dataAttributes'; + +interface ListWrapperProps { + index: number | undefined; + title?: string | null; + action?: string | null; + dataAttributes?: DataAttributes; + className?: string; +} + +const ListWrapper: FC = ({ + index, + action, + title, + className, + dataAttributes, + children +}) => { + if (layoutManager.tv) { + return ( + + ); + } else { + return ( + + {children} + + ); + } +}; + +export default ListWrapper; diff --git a/src/components/listview/List/Lists.tsx b/src/components/listview/List/Lists.tsx new file mode 100644 index 0000000000..ce90622c1f --- /dev/null +++ b/src/components/listview/List/Lists.tsx @@ -0,0 +1,57 @@ +import React, { FC } from 'react'; +import escapeHTML from 'escape-html'; +import { groupBy } from 'lodash-es'; +import Box from '@mui/material/Box'; +import { getIndex } from './listHelper'; +import ListGroupHeaderWrapper from './ListGroupHeaderWrapper'; +import List from './List'; + +import type { ItemDto } from 'types/itemDto'; +import type { ListOptions } from 'types/listOptions'; +import '../listview.scss'; + +interface ListsProps { + items: ItemDto[]; + listOptions?: ListOptions; +} + +const Lists: FC = ({ items = [], listOptions = {} }) => { + const groupedData = groupBy(items, (item) => { + if (listOptions.showIndex) { + return getIndex(item, listOptions); + } + return ''; + }); + + const renderListItem = (item: ItemDto, index: number) => { + return ( + + ); + }; + + return ( + <> + {Object.entries(groupedData).map( + ([itemGroupTitle, getItems], index) => ( + // eslint-disable-next-line react/no-array-index-key + + {itemGroupTitle && ( + + {escapeHTML(itemGroupTitle)} + + )} + {getItems.map((item) => renderListItem(item, index))} + + ) + )} + + ); +}; + +export default Lists; diff --git a/src/components/listview/List/listHelper.ts b/src/components/listview/List/listHelper.ts new file mode 100644 index 0000000000..d2ab794ea4 --- /dev/null +++ b/src/components/listview/List/listHelper.ts @@ -0,0 +1,171 @@ +import { Api } from '@jellyfin/sdk'; +import { BaseItemKind, ImageType } from '@jellyfin/sdk/lib/generated-client'; +import globalize from 'scripts/globalize'; + +import type { ItemDto } from 'types/itemDto'; +import type { ListOptions } from 'types/listOptions'; + +const sortBySortName = (item: ItemDto): string => { + if (item.Type === BaseItemKind.Episode) { + return ''; + } + + // SortName + const name = (item.SortName ?? item.Name ?? '?')[0].toUpperCase(); + + const code = name.charCodeAt(0); + if (code < 65 || code > 90) { + return '#'; + } + + return name.toUpperCase(); +}; + +const sortByOfficialrating = (item: ItemDto): string => { + return item.OfficialRating ?? globalize.translate('Unrated'); +}; + +const sortByCommunityRating = (item: ItemDto): string => { + if (item.CommunityRating == null) { + return globalize.translate('Unrated'); + } + + return String(Math.floor(item.CommunityRating)); +}; + +const sortByCriticRating = (item: ItemDto): string => { + if (item.CriticRating == null) { + return globalize.translate('Unrated'); + } + + return String(Math.floor(item.CriticRating)); +}; + +const sortByAlbumArtist = (item: ItemDto): string => { + // SortName + if (!item.AlbumArtist) { + return ''; + } + + const name = item.AlbumArtist[0].toUpperCase(); + + const code = name.charCodeAt(0); + if (code < 65 || code > 90) { + return '#'; + } + + return name.toUpperCase(); +}; + +export function getIndex(item: ItemDto, listOptions: ListOptions): string { + if (listOptions.index === 'disc') { + return item.ParentIndexNumber == null ? + '' : + globalize.translate('ValueDiscNumber', item.ParentIndexNumber); + } + + const sortBy = (listOptions.sortBy ?? '').toLowerCase(); + + if (sortBy.startsWith('sortname')) { + return sortBySortName(item); + } + if (sortBy.startsWith('officialrating')) { + return sortByOfficialrating(item); + } + if (sortBy.startsWith('communityrating')) { + return sortByCommunityRating(item); + } + if (sortBy.startsWith('criticrating')) { + return sortByCriticRating(item); + } + if (sortBy.startsWith('albumartist')) { + return sortByAlbumArtist(item); + } + return ''; +} + +export function getImageUrl( + item: ItemDto, + api: Api | undefined, + size: number | undefined +) { + let imgTag; + let itemId; + const fillWidth = size; + const fillHeight = size; + const imgType = ImageType.Primary; + + if (item.ImageTags?.Primary) { + imgTag = item.ImageTags.Primary; + itemId = item.Id; + } else if (item.AlbumId && item.AlbumPrimaryImageTag) { + imgTag = item.AlbumPrimaryImageTag; + itemId = item.AlbumId; + } else if (item.SeriesId && item.SeriesPrimaryImageTag) { + imgTag = item.SeriesPrimaryImageTag; + itemId = item.SeriesId; + } else if (item.ParentPrimaryImageTag) { + imgTag = item.ParentPrimaryImageTag; + itemId = item.ParentPrimaryImageItemId; + } + + if (api && imgTag && imgType && itemId) { + const response = api.getItemImageUrl(itemId, imgType, { + fillWidth: fillWidth, + fillHeight: fillHeight, + tag: imgTag + }); + + return { + imgUrl: response, + blurhash: item.ImageBlurHashes?.[imgType]?.[imgTag] + }; + } + + return { + imgUrl: undefined, + blurhash: undefined + }; +} + +export function getChannelImageUrl( + item: ItemDto, + api: Api | undefined, + size: number | undefined +) { + let imgTag; + let itemId; + const fillWidth = size; + const fillHeight = size; + const imgType = ImageType.Primary; + + if (item.ChannelId && item.ChannelPrimaryImageTag) { + imgTag = item.ChannelPrimaryImageTag; + itemId = item.ChannelId; + } + + if (api && imgTag && imgType && itemId) { + const response = api.getItemImageUrl(itemId, imgType, { + fillWidth: fillWidth, + fillHeight: fillHeight, + tag: imgTag + }); + + return { + imgUrl: response, + blurhash: item.ImageBlurHashes?.[imgType]?.[imgTag] + }; + } + + return { + imgUrl: undefined, + blurhash: undefined + }; +} + +export function canResume(PlaybackPositionTicks: number | undefined): boolean { + return Boolean( + PlaybackPositionTicks + && PlaybackPositionTicks > 0 + ); +} diff --git a/src/components/listview/List/useList.ts b/src/components/listview/List/useList.ts new file mode 100644 index 0000000000..75a60c6b54 --- /dev/null +++ b/src/components/listview/List/useList.ts @@ -0,0 +1,77 @@ +import classNames from 'classnames'; +import { getDataAttributes } from 'utils/items'; +import layoutManager from 'components/layoutManager'; + +import type { ItemDto } from 'types/itemDto'; +import type { ListOptions } from 'types/listOptions'; + +interface UseListProps { + item: ItemDto; + listOptions: ListOptions; +} + +function useList({ item, listOptions }: UseListProps) { + const action = listOptions.action ?? 'link'; + const isLargeStyle = listOptions.imageSize === 'large'; + const enableOverview = listOptions.enableOverview; + const clickEntireItem = !!layoutManager.tv; + const enableSideMediaInfo = listOptions.enableSideMediaInfo ?? true; + const enableContentWrapper = + listOptions.enableOverview && !layoutManager.tv; + const downloadWidth = isLargeStyle ? 500 : 80; + + const dataAttributes = getDataAttributes( + { + action, + itemServerId: item.ServerId, + itemId: item.Id, + collectionId: listOptions.collectionId, + playlistId: listOptions.playlistId, + itemChannelId: item.ChannelId, + itemType: item.Type, + itemMediaType: item.MediaType, + itemCollectionType: item.CollectionType, + itemIsFolder: item.IsFolder, + itemPlaylistItemId: item.PlaylistItemId + } + ); + + const listWrapperClass = classNames( + 'listItem', + { + 'listItem-border': + listOptions.border + ?? (listOptions.highlight !== false && !layoutManager.tv) + }, + { 'itemAction listItem-button': clickEntireItem }, + { 'listItem-focusscale': layoutManager.tv }, + { 'listItem-largeImage': isLargeStyle }, + { 'listItem-withContentWrapper': enableContentWrapper } + ); + + const getListdWrapperProps = () => ({ + className: listWrapperClass, + title: item.Name, + action, + dataAttributes + }); + + const getListContentProps = () => ({ + item, + listOptions, + enableContentWrapper, + enableOverview, + enableSideMediaInfo, + clickEntireItem, + action, + isLargeStyle, + downloadWidth + }); + + return { + getListdWrapperProps, + getListContentProps + }; +} + +export default useList; diff --git a/src/components/listview/List/useListTextlines.tsx b/src/components/listview/List/useListTextlines.tsx new file mode 100644 index 0000000000..cb5f7ceeb8 --- /dev/null +++ b/src/components/listview/List/useListTextlines.tsx @@ -0,0 +1,167 @@ +import React from 'react'; +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; +import itemHelper from '../../itemHelper'; +import datetime from 'scripts/datetime'; +import ListTextWrapper from './ListTextWrapper'; +import type { ItemDto } from 'types/itemDto'; +import type { ListOptions } from 'types/listOptions'; + +function getParentTitle( + showParentTitle: boolean | undefined, + item: ItemDto, + parentTitleWithTitle: boolean | undefined, + displayName: string | null | undefined +) { + let parentTitle = null; + if (showParentTitle) { + if (item.Type === BaseItemKind.Episode) { + parentTitle = item.SeriesName; + } else if (item.IsSeries || (item.EpisodeTitle && item.Name)) { + parentTitle = item.Name; + } + } + if (showParentTitle && parentTitleWithTitle) { + if (displayName) { + parentTitle += ' - '; + } + parentTitle = (parentTitle ?? '') + displayName; + } + return parentTitle; +} + +function getNameOrIndexWithName( + item: ItemDto, + listOptions: ListOptions, + showIndexNumber: boolean | undefined +) { + let displayName = itemHelper.getDisplayName(item, { + includeParentInfo: listOptions.includeParentInfoInTitle + }); + + if (showIndexNumber && item.IndexNumber != null) { + displayName = `${item.IndexNumber}. ${displayName}`; + } + return displayName; +} + +interface UseListTextlinesProps { + item: ItemDto; + listOptions?: ListOptions; + isLargeStyle?: boolean; +} + +function useListTextlines({ item = {}, listOptions = {}, isLargeStyle }: UseListTextlinesProps) { + const { + showProgramDateTime, + showProgramTime, + showChannel, + showParentTitle, + showIndexNumber, + parentTitleWithTitle, + artist + } = listOptions; + const textLines: string[] = []; + + const addTextLine = (text: string | null) => { + if (text) { + textLines.push(text); + } + }; + + const addProgramDateTime = () => { + if (showProgramDateTime) { + const programDateTime = datetime.toLocaleString( + datetime.parseISO8601Date(item.StartDate), + { + weekday: 'long', + month: 'short', + day: 'numeric', + hour: 'numeric', + minute: '2-digit' + } + ); + addTextLine(programDateTime); + } + }; + + const addProgramTime = () => { + if (showProgramTime) { + const programTime = datetime.getDisplayTime( + datetime.parseISO8601Date(item.StartDate) + ); + addTextLine(programTime); + } + }; + + const addChannelName = () => { + if (showChannel && item.ChannelName) { + addTextLine(item.ChannelName); + } + }; + + const displayName = getNameOrIndexWithName(item, listOptions, showIndexNumber); + + const parentTitle = getParentTitle(showParentTitle, item, parentTitleWithTitle, displayName ); + + const addParentTitle = () => { + addTextLine(parentTitle ?? ''); + }; + + const addDisplayName = () => { + if (displayName && !parentTitleWithTitle) { + addTextLine(displayName); + } + }; + + const addAlbumArtistOrArtists = () => { + if (item.IsFolder && artist !== false) { + if (item.AlbumArtist && item.Type === BaseItemKind.MusicAlbum) { + addTextLine(item.AlbumArtist); + } + } else if (artist) { + const artistItems = item.ArtistItems; + if (artistItems && item.Type !== BaseItemKind.MusicAlbum) { + const artists = artistItems.map((a) => a.Name).join(', '); + addTextLine(artists); + } + } + }; + + const addCurrentProgram = () => { + if (item.Type === BaseItemKind.TvChannel && item.CurrentProgram) { + const currentProgram = itemHelper.getDisplayName( + item.CurrentProgram + ); + addTextLine(currentProgram); + } + }; + + addProgramDateTime(); + addProgramTime(); + addChannelName(); + addParentTitle(); + addDisplayName(); + addAlbumArtistOrArtists(); + addCurrentProgram(); + + const renderTextlines = (text: string, index: number) => { + return ( + + {text} + + ); + }; + + const listTextLines = textLines?.map((text, index) => renderTextlines(text, index)); + + return { + listTextLines + }; +} + +export default useListTextlines; diff --git a/src/components/listview/listview.scss b/src/components/listview/listview.scss index 2aafd936a2..ea829154bb 100644 --- a/src/components/listview/listview.scss +++ b/src/components/listview/listview.scss @@ -183,6 +183,7 @@ } .listItemImage .cardImageIcon { + margin: auto; font-size: 3em; } From 97472ac8bb4d54b4c6323cf7c8d79288d8342297 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 31 Jan 2024 04:25:14 +0300 Subject: [PATCH 07/90] Convert CardView to react --- src/components/cardbuilder/Card/Card.tsx | 25 + src/components/cardbuilder/Card/CardBox.tsx | 79 ++ .../cardbuilder/Card/CardContent.tsx | 50 ++ .../cardbuilder/Card/CardFooterText.tsx | 81 ++ .../cardbuilder/Card/CardHoverMenu.tsx | 83 ++ .../cardbuilder/Card/CardImageContainer.tsx | 82 ++ .../cardbuilder/Card/CardInnerFooter.tsx | 42 + .../cardbuilder/Card/CardOuterFooter.tsx | 45 ++ .../cardbuilder/Card/CardOverlayButtons.tsx | 96 +++ src/components/cardbuilder/Card/CardText.tsx | 33 + .../cardbuilder/Card/CardWrapper.tsx | 30 + src/components/cardbuilder/Card/Cards.tsx | 32 + src/components/cardbuilder/Card/cardHelper.ts | 721 ++++++++++++++++++ src/components/cardbuilder/Card/useCard.ts | 121 +++ .../cardbuilder/Card/useCardImageUrl.ts | 301 ++++++++ .../cardbuilder/Card/useCardText.tsx | 113 +++ src/components/cardbuilder/card.scss | 7 +- src/components/cardbuilder/cardBuilder.js | 2 +- .../cardbuilder/cardBuilderUtils.ts | 4 +- src/types/cardOptions.ts | 57 +- 20 files changed, 1993 insertions(+), 11 deletions(-) create mode 100644 src/components/cardbuilder/Card/Card.tsx create mode 100644 src/components/cardbuilder/Card/CardBox.tsx create mode 100644 src/components/cardbuilder/Card/CardContent.tsx create mode 100644 src/components/cardbuilder/Card/CardFooterText.tsx create mode 100644 src/components/cardbuilder/Card/CardHoverMenu.tsx create mode 100644 src/components/cardbuilder/Card/CardImageContainer.tsx create mode 100644 src/components/cardbuilder/Card/CardInnerFooter.tsx create mode 100644 src/components/cardbuilder/Card/CardOuterFooter.tsx create mode 100644 src/components/cardbuilder/Card/CardOverlayButtons.tsx create mode 100644 src/components/cardbuilder/Card/CardText.tsx create mode 100644 src/components/cardbuilder/Card/CardWrapper.tsx create mode 100644 src/components/cardbuilder/Card/Cards.tsx create mode 100644 src/components/cardbuilder/Card/cardHelper.ts create mode 100644 src/components/cardbuilder/Card/useCard.ts create mode 100644 src/components/cardbuilder/Card/useCardImageUrl.ts create mode 100644 src/components/cardbuilder/Card/useCardText.tsx diff --git a/src/components/cardbuilder/Card/Card.tsx b/src/components/cardbuilder/Card/Card.tsx new file mode 100644 index 0000000000..2173e0301b --- /dev/null +++ b/src/components/cardbuilder/Card/Card.tsx @@ -0,0 +1,25 @@ +import React, { FC } from 'react'; +import useCard from './useCard'; +import CardWrapper from './CardWrapper'; +import CardBox from './CardBox'; + +import type { CardOptions } from 'types/cardOptions'; +import type { ItemDto } from 'types/itemDto'; + +interface CardProps { + item?: ItemDto; + cardOptions: CardOptions; +} + +const Card: FC = ({ item = {}, cardOptions }) => { + const { getCardWrapperProps, getCardBoxProps } = useCard({ item, cardOptions } ); + const cardWrapperProps = getCardWrapperProps(); + const cardBoxProps = getCardBoxProps(); + return ( + + + + ); +}; + +export default Card; diff --git a/src/components/cardbuilder/Card/CardBox.tsx b/src/components/cardbuilder/Card/CardBox.tsx new file mode 100644 index 0000000000..430c27b444 --- /dev/null +++ b/src/components/cardbuilder/Card/CardBox.tsx @@ -0,0 +1,79 @@ + +import React, { FC } from 'react'; +import layoutManager from 'components/layoutManager'; + +import CardOverlayButtons from './CardOverlayButtons'; +import CardHoverMenu from './CardHoverMenu'; +import CardOuterFooter from './CardOuterFooter'; +import CardContent from './CardContent'; + +import type { ItemDto } from 'types/itemDto'; +import type { CardOptions } from 'types/cardOptions'; + +interface CardBoxProps { + item: ItemDto; + cardOptions: CardOptions; + className: string; + shape: string | null | undefined; + imgUrl: string | undefined; + blurhash: string | undefined; + forceName: boolean; + coveredImage: boolean; + overlayText: boolean | undefined; +} + +const CardBox: FC = ({ + item, + cardOptions, + className, + shape, + imgUrl, + blurhash, + forceName, + coveredImage, + overlayText +}) => { + return ( +
+
+
+ + {layoutManager.mobile && ( + + )} + + {layoutManager.desktop + && !cardOptions.disableHoverMenu && ( + + )} +
+ {!overlayText && ( + + )} +
+ ); +}; + +export default CardBox; + diff --git a/src/components/cardbuilder/Card/CardContent.tsx b/src/components/cardbuilder/Card/CardContent.tsx new file mode 100644 index 0000000000..3d846758a4 --- /dev/null +++ b/src/components/cardbuilder/Card/CardContent.tsx @@ -0,0 +1,50 @@ +import React, { FC } from 'react'; +import classNames from 'classnames'; +import { getDefaultBackgroundClass } from '../cardBuilderUtils'; +import CardImageContainer from './CardImageContainer'; + +import type { ItemDto } from 'types/itemDto'; +import type { CardOptions } from 'types/cardOptions'; + +interface CardContentProps { + item: ItemDto; + cardOptions: CardOptions; + coveredImage: boolean; + overlayText: boolean | undefined; + imgUrl: string | undefined; + blurhash: string | undefined; + forceName: boolean; +} + +const CardContent: FC = ({ + item, + cardOptions, + coveredImage, + overlayText, + imgUrl, + blurhash, + forceName +}) => { + const cardContentClass = classNames( + 'cardContent', + { [getDefaultBackgroundClass(item.Name)]: !imgUrl } + ); + + return ( +
+ +
+ ); +}; + +export default CardContent; diff --git a/src/components/cardbuilder/Card/CardFooterText.tsx b/src/components/cardbuilder/Card/CardFooterText.tsx new file mode 100644 index 0000000000..87ba3b22ea --- /dev/null +++ b/src/components/cardbuilder/Card/CardFooterText.tsx @@ -0,0 +1,81 @@ +import React, { FC } from 'react'; +import Box from '@mui/material/Box'; +import useCardText from './useCardText'; +import layoutManager from 'components/layoutManager'; +import MoreVertIconButton from '../../common/MoreVertIconButton'; +import type { ItemDto } from 'types/itemDto'; +import type { CardOptions } from 'types/cardOptions'; + +const shouldShowDetailsMenu = ( + cardOptions: CardOptions, + isOuterFooter: boolean +) => { + return ( + cardOptions.showDetailsMenu + && isOuterFooter + && cardOptions.cardLayout + && layoutManager.mobile + && cardOptions.cardFooterAside !== 'none' + ); +}; + +interface LogoComponentProps { + logoUrl: string; +} + +const LogoComponent: FC = ({ logoUrl }) => { + return ; +}; + +interface CardFooterTextProps { + item: ItemDto; + cardOptions: CardOptions; + forceName: boolean; + overlayText: boolean | undefined; + imgUrl: string | undefined; + footerClass: string | undefined; + progressBar?: React.JSX.Element | null; + logoUrl?: string; + isOuterFooter: boolean; +} + +const CardFooterText: FC = ({ + item, + cardOptions, + forceName, + imgUrl, + footerClass, + overlayText, + progressBar, + logoUrl, + isOuterFooter +}) => { + const { cardTextLines } = useCardText({ + item, + cardOptions, + forceName, + imgUrl, + overlayText, + isOuterFooter, + cssClass: cardOptions.centerText ? + 'cardText cardTextCentered' : + 'cardText', + forceLines: !cardOptions.overlayText, + maxLines: cardOptions.lines + }); + + return ( + + {logoUrl && } + {shouldShowDetailsMenu(cardOptions, isOuterFooter) && ( + + )} + + {cardTextLines} + + {progressBar} + + ); +}; + +export default CardFooterText; diff --git a/src/components/cardbuilder/Card/CardHoverMenu.tsx b/src/components/cardbuilder/Card/CardHoverMenu.tsx new file mode 100644 index 0000000000..e135d1bd82 --- /dev/null +++ b/src/components/cardbuilder/Card/CardHoverMenu.tsx @@ -0,0 +1,83 @@ +import React, { FC } from 'react'; +import Box from '@mui/material/Box'; +import ButtonGroup from '@mui/material/ButtonGroup'; +import classNames from 'classnames'; +import escapeHTML from 'escape-html'; +import { appRouter } from 'components/router/appRouter'; +import itemHelper from 'components/itemHelper'; +import { playbackManager } from 'components/playback/playbackmanager'; + +import PlayedButton from 'elements/emby-playstatebutton/PlayedButton'; +import FavoriteButton from 'elements/emby-ratingbutton/FavoriteButton'; +import PlayArrowIconButton from '../../common/PlayArrowIconButton'; +import MoreVertIconButton from '../../common/MoreVertIconButton'; + +import type { ItemDto } from 'types/itemDto'; +import type { CardOptions } from 'types/cardOptions'; + +interface CardHoverMenuProps { + item: ItemDto; + cardOptions: CardOptions; +} + +const CardHoverMenu: FC = ({ + item, + cardOptions +}) => { + const url = appRouter.getRouteUrl(item, { + parentId: cardOptions.parentId + }); + const btnCssClass = + 'paper-icon-button-light cardOverlayButton cardOverlayButton-hover itemAction'; + + const centerPlayButtonClass = classNames( + btnCssClass, + 'cardOverlayFab-primary' + ); + const { IsFavorite, Played } = item.UserData ?? {}; + + return ( + + + + {playbackManager.canPlay(item) && ( + + )} + + + {itemHelper.canMarkPlayed(item) && cardOptions.enablePlayedButton !== false && ( + + )} + + {itemHelper.canRate(item) && cardOptions.enableRatingButton !== false && ( + + )} + + + + + ); +}; + +export default CardHoverMenu; diff --git a/src/components/cardbuilder/Card/CardImageContainer.tsx b/src/components/cardbuilder/Card/CardImageContainer.tsx new file mode 100644 index 0000000000..3b66048e9e --- /dev/null +++ b/src/components/cardbuilder/Card/CardImageContainer.tsx @@ -0,0 +1,82 @@ +import React, { FC } from 'react'; +import Box from '@mui/material/Box'; +import classNames from 'classnames'; +import useIndicator from 'components/indicators/useIndicator'; +import RefreshIndicator from 'elements/emby-itemrefreshindicator/RefreshIndicator'; +import Media from '../../common/Media'; +import CardInnerFooter from './CardInnerFooter'; + +import type { ItemDto } from 'types/itemDto'; +import type { CardOptions } from 'types/cardOptions'; + +interface CardImageContainerProps { + item: ItemDto; + cardOptions: CardOptions; + coveredImage: boolean; + overlayText: boolean | undefined; + imgUrl: string | undefined; + blurhash: string | undefined; + forceName: boolean; +} + +const CardImageContainer: FC = ({ + item, + cardOptions, + coveredImage, + overlayText, + imgUrl, + blurhash, + forceName +}) => { + const indicator = useIndicator(item); + const cardImageClass = classNames( + 'cardImageContainer', + { coveredImage: coveredImage }, + { 'coveredImage-contain': coveredImage && item.Type === 'TvChannel' } + ); + + return ( +
+ {cardOptions.disableIndicators !== true && ( + + {indicator.getMediaSourceIndicator()} + + + {cardOptions.missingIndicator !== false + && indicator.getMissingIndicator()} + + {indicator.getTimerIndicator()} + {indicator.getTypeIndicator()} + + {cardOptions.showGroupCount ? + indicator.getChildCountIndicator() : + indicator.getPlayedIndicator()} + + {(item.Type === 'CollectionFolder' + || item.CollectionType) + && item.RefreshProgress && ( + + )} + + + )} + + + + {overlayText && ( + + )} + + {!overlayText && indicator.getProgressBar()} +
+ ); +}; + +export default CardImageContainer; diff --git a/src/components/cardbuilder/Card/CardInnerFooter.tsx b/src/components/cardbuilder/Card/CardInnerFooter.tsx new file mode 100644 index 0000000000..d6edf853c0 --- /dev/null +++ b/src/components/cardbuilder/Card/CardInnerFooter.tsx @@ -0,0 +1,42 @@ +import React, { FC } from 'react'; +import classNames from 'classnames'; +import CardFooterText from './CardFooterText'; +import type { ItemDto } from 'types/itemDto'; +import type { CardOptions } from 'types/cardOptions'; + +interface CardInnerFooterProps { + item: ItemDto; + cardOptions: CardOptions; + imgUrl: string | undefined; + progressBar?: React.JSX.Element | null; + forceName: boolean; + overlayText: boolean | undefined; +} + +const CardInnerFooter: FC = ({ + item, + cardOptions, + imgUrl, + overlayText, + progressBar, + forceName +}) => { + const footerClass = classNames('innerCardFooter', { + fullInnerCardFooter: progressBar + }); + + return ( + + ); +}; + +export default CardInnerFooter; diff --git a/src/components/cardbuilder/Card/CardOuterFooter.tsx b/src/components/cardbuilder/Card/CardOuterFooter.tsx new file mode 100644 index 0000000000..020a64d584 --- /dev/null +++ b/src/components/cardbuilder/Card/CardOuterFooter.tsx @@ -0,0 +1,45 @@ +import React, { FC } from 'react'; +import classNames from 'classnames'; +import { useApi } from 'hooks/useApi'; +import { getCardLogoUrl } from './cardHelper'; +import CardFooterText from './CardFooterText'; + +import type { ItemDto } from 'types/itemDto'; +import type { CardOptions } from 'types/cardOptions'; + +interface CardOuterFooterProps { + item: ItemDto + cardOptions: CardOptions; + imgUrl: string | undefined; + forceName: boolean; + overlayText: boolean | undefined +} + +const CardOuterFooter: FC = ({ item, cardOptions, overlayText, imgUrl, forceName }) => { + const { api } = useApi(); + const logoInfo = getCardLogoUrl(item, api, cardOptions); + const logoUrl = logoInfo.logoUrl; + + const footerClass = classNames( + 'cardFooter', + { 'cardFooter-transparent': cardOptions.cardLayout }, + { 'cardFooter-withlogo': logoUrl } + ); + + return ( + + + ); +}; + +export default CardOuterFooter; diff --git a/src/components/cardbuilder/Card/CardOverlayButtons.tsx b/src/components/cardbuilder/Card/CardOverlayButtons.tsx new file mode 100644 index 0000000000..0ac8463969 --- /dev/null +++ b/src/components/cardbuilder/Card/CardOverlayButtons.tsx @@ -0,0 +1,96 @@ +import React, { FC } from 'react'; +import Box from '@mui/material/Box'; +import ButtonGroup from '@mui/material/ButtonGroup'; +import classNames from 'classnames'; + +import PlayArrowIconButton from '../../common/PlayArrowIconButton'; +import MoreVertIconButton from '../../common/MoreVertIconButton'; + +import type { ItemDto } from 'types/itemDto'; +import type { CardOptions } from 'types/cardOptions'; + +const sholudShowOverlayPlayButton = ( + overlayPlayButton: boolean | undefined, + item: ItemDto +) => { + return ( + overlayPlayButton + && !item.IsPlaceHolder + && (item.LocationType !== 'Virtual' + || !item.MediaType + || item.Type === 'Program') + && item.Type !== 'Person' + ); +}; + +interface CardOverlayButtonsProps { + item: ItemDto; + cardOptions: CardOptions; +} + +const CardOverlayButtons: FC = ({ + item, + cardOptions +}) => { + let overlayPlayButton = cardOptions.overlayPlayButton; + + if ( + overlayPlayButton == null + && !cardOptions.overlayMoreButton + && !cardOptions.overlayInfoButton + && !cardOptions.cardLayout + ) { + overlayPlayButton = item.MediaType === 'Video'; + } + + const btnCssClass = classNames( + 'paper-icon-button-light', + 'cardOverlayButton', + 'itemAction' + ); + + const centerPlayButtonClass = classNames( + btnCssClass, + 'cardOverlayButton-centered' + ); + + return ( + + {cardOptions.centerPlayButton && ( + + )} + + + {sholudShowOverlayPlayButton(overlayPlayButton, item) && ( + + )} + + {cardOptions.overlayMoreButton && ( + + )} + + + ); +}; + +export default CardOverlayButtons; diff --git a/src/components/cardbuilder/Card/CardText.tsx b/src/components/cardbuilder/Card/CardText.tsx new file mode 100644 index 0000000000..be6d0b049c --- /dev/null +++ b/src/components/cardbuilder/Card/CardText.tsx @@ -0,0 +1,33 @@ +import React, { FC } from 'react'; +import Box from '@mui/material/Box'; +import escapeHTML from 'escape-html'; +import type { TextLine } from './cardHelper'; + +interface CardTextProps { + className?: string; + textLine: TextLine; +} + +const CardText: FC = ({ className, textLine }) => { + const { title, titleAction } = textLine; + const renderCardText = () => { + if (titleAction) { + return ( + + {escapeHTML(titleAction.title)} + + ); + } else { + return title; + } + }; + + return {renderCardText()}; +}; + +export default CardText; diff --git a/src/components/cardbuilder/Card/CardWrapper.tsx b/src/components/cardbuilder/Card/CardWrapper.tsx new file mode 100644 index 0000000000..01d6446a91 --- /dev/null +++ b/src/components/cardbuilder/Card/CardWrapper.tsx @@ -0,0 +1,30 @@ +import React, { FC } from 'react'; +import layoutManager from 'components/layoutManager'; +import type { DataAttributes } from 'types/dataAttributes'; + +interface CardWrapperProps { + className: string; + dataAttributes: DataAttributes; +} + +const CardWrapper: FC = ({ + className, + dataAttributes, + children +}) => { + if (layoutManager.tv) { + return ( + + ); + } else { + return ( +
+ {children} +
+ ); + } +}; + +export default CardWrapper; diff --git a/src/components/cardbuilder/Card/Cards.tsx b/src/components/cardbuilder/Card/Cards.tsx new file mode 100644 index 0000000000..fcf2454a57 --- /dev/null +++ b/src/components/cardbuilder/Card/Cards.tsx @@ -0,0 +1,32 @@ +import React, { FC } from 'react'; +import { setCardData } from '../cardBuilder'; +import Card from './Card'; +import type { ItemDto } from 'types/itemDto'; +import type { CardOptions } from 'types/cardOptions'; +import '../card.scss'; + +interface CardsProps { + items: ItemDto[]; + cardOptions: CardOptions; +} + +const Cards: FC = ({ + items = [], + cardOptions +}) => { + setCardData(items, cardOptions); + return ( + // eslint-disable-next-line react/jsx-no-useless-fragment + <> + {items?.map((item) => ( + + ))} + + ); +}; + +export default Cards; diff --git a/src/components/cardbuilder/Card/cardHelper.ts b/src/components/cardbuilder/Card/cardHelper.ts new file mode 100644 index 0000000000..b6f8c37ab1 --- /dev/null +++ b/src/components/cardbuilder/Card/cardHelper.ts @@ -0,0 +1,721 @@ +import { + BaseItemDto, + BaseItemKind, + BaseItemPerson, + ImageType +} from '@jellyfin/sdk/lib/generated-client'; +import { Api } from '@jellyfin/sdk'; +import escapeHTML from 'escape-html'; + +import { appRouter } from 'components/router/appRouter'; +import layoutManager from 'components/layoutManager'; +import itemHelper from 'components/itemHelper'; +import globalize from 'scripts/globalize'; +import datetime from 'scripts/datetime'; + +import { isUsingLiveTvNaming } from '../cardBuilderUtils'; + +import type { ItemDto, NullableNumber, NullableString } from 'types/itemDto'; +import type { CardOptions } from 'types/cardOptions'; +import type { DataAttributes } from 'types/dataAttributes'; +import { getDataAttributes } from 'utils/items'; + +export function getCardLogoUrl( + item: ItemDto, + api: Api | undefined, + cardOptions: CardOptions +) { + let imgType; + let imgTag; + let itemId; + const logoHeight = 40; + + if (cardOptions.showChannelLogo && item.ChannelPrimaryImageTag) { + imgType = ImageType.Primary; + imgTag = item.ChannelPrimaryImageTag; + itemId = item.ChannelId; + } else if (cardOptions.showLogo && item.ParentLogoImageTag) { + imgType = ImageType.Logo; + imgTag = item.ParentLogoImageTag; + itemId = item.ParentLogoItemId; + } + + if (!itemId) { + itemId = item.Id; + } + + if (api && imgTag && imgType && itemId) { + const response = api.getItemImageUrl(itemId, imgType, { + height: logoHeight, + tag: imgTag + }); + + return { + logoUrl: response + }; + } + + return { + logoUrl: undefined + }; +} + +interface TextAction { + url: string; + title: string; + dataAttributes: DataAttributes +} + +export interface TextLine { + title?: NullableString; + titleAction?: TextAction; +} + +export function getTextActionButton( + item: ItemDto, + text?: NullableString, + serverId?: NullableString +): TextLine { + if (!text) { + text = itemHelper.getDisplayName(item); + } + + text = escapeHTML(text); + + if (layoutManager.tv) { + return { + title: text + }; + } + + const url = appRouter.getRouteUrl(item); + + const dataAttributes = getDataAttributes( + { + action: 'link', + itemServerId: serverId ?? item.ServerId, + itemId: item.Id, + itemChannelId: item.ChannelId, + itemType: item.Type, + itemMediaType: item.MediaType, + itemCollectionType: item.CollectionType, + itemIsFolder: item.IsFolder + } + ); + + return { + titleAction: { + url, + title: text, + dataAttributes + } + }; +} + +export function getAirTimeText( + item: ItemDto, + showAirDateTime: boolean | undefined, + showAirEndTime: boolean | undefined +) { + let airTimeText = ''; + + if (item.StartDate) { + try { + let date = datetime.parseISO8601Date(item.StartDate); + + if (showAirDateTime) { + airTimeText + += datetime.toLocaleDateString(date, { + weekday: 'short', + month: 'short', + day: 'numeric' + }) + ' '; + } + + airTimeText += datetime.getDisplayTime(date); + + if (item.EndDate && showAirEndTime) { + date = datetime.parseISO8601Date(item.EndDate); + airTimeText += ' - ' + datetime.getDisplayTime(date); + } + } catch (e) { + console.error('error parsing date: ' + item.StartDate); + } + } + return airTimeText; +} + +function isGenreOrStudio(itemType: NullableString) { + return itemType === BaseItemKind.Genre || itemType === BaseItemKind.Studio; +} + +function isMusicGenreOrMusicArtist( + itemType: NullableString, + context: NullableString +) { + return itemType === BaseItemKind.MusicGenre || context === 'MusicArtist'; +} + +function getMovieCount(itemMovieCount: NullableNumber) { + if (itemMovieCount) { + return itemMovieCount === 1 ? + globalize.translate('ValueOneMovie') : + globalize.translate('ValueMovieCount', itemMovieCount); + } +} + +function getSeriesCount(itemSeriesCount: NullableNumber) { + if (itemSeriesCount) { + return itemSeriesCount === 1 ? + globalize.translate('ValueOneSeries') : + globalize.translate('ValueSeriesCount', itemSeriesCount); + } +} + +function getEpisodeCount(itemEpisodeCount: NullableNumber) { + if (itemEpisodeCount) { + return itemEpisodeCount === 1 ? + globalize.translate('ValueOneEpisode') : + globalize.translate('ValueEpisodeCount', itemEpisodeCount); + } +} + +function getAlbumCount(itemAlbumCount: NullableNumber) { + if (itemAlbumCount) { + return itemAlbumCount === 1 ? + globalize.translate('ValueOneAlbum') : + globalize.translate('ValueAlbumCount', itemAlbumCount); + } +} + +function getSongCount(itemSongCount: NullableNumber) { + if (itemSongCount) { + return itemSongCount === 1 ? + globalize.translate('ValueOneSong') : + globalize.translate('ValueSongCount', itemSongCount); + } +} + +function getMusicVideoCount(itemMusicVideoCount: NullableNumber) { + if (itemMusicVideoCount) { + return itemMusicVideoCount === 1 ? + globalize.translate('ValueOneMusicVideo') : + globalize.translate('ValueMusicVideoCount', itemMusicVideoCount); + } +} + +function getRecursiveItemCount(itemRecursiveItemCount: NullableNumber) { + return itemRecursiveItemCount === 1 ? + globalize.translate('ValueOneEpisode') : + globalize.translate('ValueEpisodeCount', itemRecursiveItemCount); +} + +function getParentTitle( + isOuterFooter: boolean, + serverId: NullableString, + item: ItemDto +) { + if (isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) { + (item.AlbumArtists[0] as BaseItemDto).Type = BaseItemKind.MusicArtist; + (item.AlbumArtists[0] as BaseItemDto).IsFolder = true; + return getTextActionButton(item.AlbumArtists[0], null, serverId); + } else { + return { + title: isUsingLiveTvNaming(item.Type) ? + item.Name : + item.SeriesName + || item.Series + || item.Album + || item.AlbumArtist + || '' + }; + } +} + +function getRunTimeTicks(itemRunTimeTicks: NullableNumber) { + if (itemRunTimeTicks) { + let minutes = itemRunTimeTicks / 600000000; + + minutes = minutes || 1; + + return globalize.translate('ValueMinutes', Math.round(minutes)); + } else { + return globalize.translate('ValueMinutes', 0); + } +} + +export function getItemCounts(cardOptions: CardOptions, item: ItemDto) { + const counts: string[] = []; + + const addCount = (text: NullableString) => { + if (text) { + counts.push(text); + } + }; + + if (item.Type === BaseItemKind.Playlist) { + const runTimeTicksText = getRunTimeTicks(item.RunTimeTicks); + addCount(runTimeTicksText); + } else if (isGenreOrStudio(item.Type)) { + const movieCountText = getMovieCount(item.MovieCount); + addCount(movieCountText); + + const seriesCountText = getSeriesCount(item.SeriesCount); + addCount(seriesCountText); + + const episodeCountText = getEpisodeCount(item.EpisodeCount); + addCount(episodeCountText); + } else if (isMusicGenreOrMusicArtist(item.Type, cardOptions.context)) { + const albumCountText = getAlbumCount(item.AlbumCount); + addCount(albumCountText); + + const songCountText = getSongCount(item.SongCount); + addCount(songCountText); + + const musicVideoCountText = getMusicVideoCount(item.MusicVideoCount); + addCount(musicVideoCountText); + } else if (item.Type === BaseItemKind.Series) { + const recursiveItemCountText = getRecursiveItemCount( + item.RecursiveItemCount + ); + addCount(recursiveItemCountText); + } + + return counts.join(', '); +} + +export function shouldShowTitle( + showTitle: boolean | string | undefined, + itemType: NullableString +) { + return ( + Boolean(showTitle) + || itemType === BaseItemKind.PhotoAlbum + || itemType === BaseItemKind.Folder + ); +} + +export function shouldShowOtherText( + isOuterFooter: boolean, + overlayText: boolean | undefined +) { + return isOuterFooter ? !overlayText : overlayText; +} + +export function shouldShowParentTitleUnderneath( + itemType: NullableString +) { + return ( + itemType === BaseItemKind.MusicAlbum + || itemType === BaseItemKind.Audio + || itemType === BaseItemKind.MusicVideo + ); +} + +function shouldShowMediaTitle( + titleAdded: boolean, + showTitle: boolean, + forceName: boolean, + cardOptions: CardOptions, + textLines: TextLine[] +) { + let showMediaTitle = + (showTitle && !titleAdded) + || (cardOptions.showParentTitleOrTitle && !textLines.length); + if (!showMediaTitle && !titleAdded && (showTitle || forceName)) { + showMediaTitle = true; + } + return showMediaTitle; +} + +function shouldShowExtraType(itemExtraType: NullableString) { + return itemExtraType && itemExtraType !== 'Unknown'; +} + +function shouldShowSeriesYearOrYear( + showYear: string | boolean | undefined, + showSeriesYear: boolean | undefined +) { + return Boolean(showYear) || showSeriesYear; +} + +function shouldShowCurrentProgram( + showCurrentProgram: boolean | undefined, + itemType: NullableString +) { + return showCurrentProgram && itemType === BaseItemKind.TvChannel; +} + +function shouldShowCurrentProgramTime( + showCurrentProgramTime: boolean | undefined, + itemType: NullableString +) { + return showCurrentProgramTime && itemType === BaseItemKind.TvChannel; +} + +function shouldShowPersonRoleOrType( + showPersonRoleOrType: boolean | undefined, + item: ItemDto +) { + return showPersonRoleOrType && (item as BaseItemPerson).Role; +} + +function shouldShowParentTitle( + showParentTitle: boolean | undefined, + parentTitleUnderneath: boolean +) { + return showParentTitle && parentTitleUnderneath; +} + +function addOtherText( + cardOptions: CardOptions, + parentTitleUnderneath: boolean, + isOuterFooter: boolean, + item: ItemDto, + addTextLine: (val: TextLine) => void, + serverId: NullableString +) { + if ( + shouldShowParentTitle( + cardOptions.showParentTitle, + parentTitleUnderneath + ) + ) { + addTextLine(getParentTitle(isOuterFooter, serverId, item)); + } + + if (shouldShowExtraType(item.ExtraType)) { + addTextLine({ title: globalize.translate(item.ExtraType) }); + } + + if (cardOptions.showItemCounts) { + addTextLine({ title: getItemCounts(cardOptions, item) }); + } + + if (cardOptions.textLines) { + addTextLine({ title: getAdditionalLines(cardOptions.textLines, item) }); + } + + if (cardOptions.showSongCount) { + addTextLine({ title: getSongCount(item.SongCount) }); + } + + if (cardOptions.showPremiereDate) { + addTextLine({ title: getPremiereDate(item.PremiereDate) }); + } + + if ( + shouldShowSeriesYearOrYear( + cardOptions.showYear, + cardOptions.showSeriesYear + ) + ) { + addTextLine({ title: getProductionYear(item) }); + } + + if (cardOptions.showRuntime) { + addTextLine({ title: getRunTime(item.RunTimeTicks) }); + } + + if (cardOptions.showAirTime) { + addTextLine({ + title: getAirTimeText( + item, + cardOptions.showAirDateTime, + cardOptions.showAirEndTime + ) + }); + } + + if (cardOptions.showChannelName) { + addTextLine(getChannelName(item)); + } + + if (shouldShowCurrentProgram(cardOptions.showCurrentProgram, item.Type)) { + addTextLine({ title: getCurrentProgramName(item.CurrentProgram) }); + } + + if ( + shouldShowCurrentProgramTime( + cardOptions.showCurrentProgramTime, + item.Type + ) + ) { + addTextLine({ title: getCurrentProgramTime(item.CurrentProgram) }); + } + + if (cardOptions.showSeriesTimerTime) { + addTextLine({ title: getSeriesTimerTime(item) }); + } + + if (cardOptions.showSeriesTimerChannel) { + addTextLine({ title: getSeriesTimerChannel(item) }); + } + + if (shouldShowPersonRoleOrType(cardOptions.showCurrentProgramTime, item)) { + addTextLine({ + title: globalize.translate( + 'PersonRole', + (item as BaseItemPerson).Role + ) + }); + } +} + +function getSeriesTimerChannel(item: ItemDto) { + if (item.RecordAnyChannel) { + return globalize.translate('AllChannels'); + } else { + return item.ChannelName || '' || globalize.translate('OneChannel'); + } +} + +function getSeriesTimerTime(item: ItemDto) { + if (item.RecordAnyTime) { + return globalize.translate('Anytime'); + } else { + return datetime.getDisplayTime(item.StartDate); + } +} + +function getCurrentProgramTime(CurrentProgram: BaseItemDto | undefined) { + if (CurrentProgram) { + return getAirTimeText(CurrentProgram, false, true) || ''; + } else { + return ''; + } +} + +function getCurrentProgramName(CurrentProgram: BaseItemDto | undefined) { + if (CurrentProgram) { + return CurrentProgram.Name; + } else { + return ''; + } +} + +function getChannelName(item: ItemDto) { + if (item.ChannelId) { + return getTextActionButton( + { + Id: item.ChannelId, + ServerId: item.ServerId, + Name: item.ChannelName, + Type: BaseItemKind.TvChannel, + MediaType: item.MediaType, + IsFolder: false + }, + item.ChannelName + ); + } else { + return { title: item.ChannelName || '' || ' ' }; + } +} + +function getRunTime(itemRunTimeTicks: NullableNumber) { + if (itemRunTimeTicks) { + return datetime.getDisplayRunningTime(itemRunTimeTicks); + } else { + return ''; + } +} + +function getPremiereDate(PremiereDate: string | null | undefined) { + if (PremiereDate) { + try { + return datetime.toLocaleDateString( + datetime.parseISO8601Date(PremiereDate), + { weekday: 'long', month: 'long', day: 'numeric' } + ); + } catch (err) { + return ''; + } + } else { + return ''; + } +} + +function getAdditionalLines( + textLines: (item: ItemDto) => (string | undefined)[], + item: ItemDto +) { + const additionalLines = textLines(item); + for (const additionalLine of additionalLines) { + return additionalLine; + } +} + +function getProductionYear(item: ItemDto) { + const productionYear = + item.ProductionYear + && datetime.toLocaleString(item.ProductionYear, { + useGrouping: false + }); + if (item.Type === BaseItemKind.Series) { + if (item.Status === 'Continuing') { + return globalize.translate( + 'SeriesYearToPresent', + productionYear || '' + ); + } else if (item.EndDate && item.ProductionYear) { + const endYear = datetime.toLocaleString( + datetime.parseISO8601Date(item.EndDate).getFullYear(), + { useGrouping: false } + ); + return ( + productionYear + + (endYear === productionYear ? '' : ' - ' + endYear) + ); + } else { + return productionYear || ''; + } + } else { + return productionYear || ''; + } +} + +function getMediaTitle(cardOptions: CardOptions, item: ItemDto): TextLine { + const name = + cardOptions.showTitle === 'auto' + && !item.IsFolder + && item.MediaType === 'Photo' ? + '' : + itemHelper.getDisplayName(item, { + includeParentInfo: cardOptions.includeParentInfoInTitle + }); + + return getTextActionButton({ + Id: item.Id, + ServerId: item.ServerId, + Name: name, + Type: item.Type, + CollectionType: item.CollectionType, + IsFolder: item.IsFolder + }); +} + +function getParentTitleOrTitle( + isOuterFooter: boolean, + item: ItemDto, + setTitleAdded: (val: boolean) => void, + showTitle: boolean +): TextLine { + if ( + isOuterFooter + && item.Type === BaseItemKind.Episode + && item.SeriesName + ) { + if (item.SeriesId) { + return getTextActionButton({ + Id: item.SeriesId, + ServerId: item.ServerId, + Name: item.SeriesName, + Type: BaseItemKind.Series, + IsFolder: true + }); + } else { + return { title: item.SeriesName }; + } + } else if (isUsingLiveTvNaming(item.Type)) { + if (!item.EpisodeTitle && !item.IndexNumber) { + setTitleAdded(true); + } + return { title: item.Name }; + } else { + const parentTitle = + item.SeriesName + || item.Series + || item.Album + || item.AlbumArtist + || ''; + + if (parentTitle || showTitle) { + return { title: parentTitle }; + } + + return { title: '' }; + } +} + +interface TextLinesOpts { + isOuterFooter: boolean; + overlayText: boolean | undefined; + forceName: boolean; + item: ItemDto; + cardOptions: CardOptions; + imgUrl: string | undefined; +} + +export function getCardTextLines({ + isOuterFooter, + overlayText, + forceName, + item, + cardOptions, + imgUrl +}: TextLinesOpts) { + const showTitle = shouldShowTitle(cardOptions.showTitle, item.Type); + const showOtherText = shouldShowOtherText(isOuterFooter, overlayText); + const serverId = item.ServerId || cardOptions.serverId; + let textLines: TextLine[] = []; + const parentTitleUnderneath = shouldShowParentTitleUnderneath(item.Type); + + let titleAdded = false; + const addTextLine = (val: TextLine) => { + textLines.push(val); + }; + + const setTitleAdded = (val: boolean) => { + titleAdded = val; + }; + + if ( + showOtherText + && (cardOptions.showParentTitle || cardOptions.showParentTitleOrTitle) + && !parentTitleUnderneath + ) { + addTextLine( + getParentTitleOrTitle(isOuterFooter, item, setTitleAdded, showTitle) + ); + } + + const showMediaTitle = shouldShowMediaTitle( + titleAdded, + showTitle, + forceName, + cardOptions, + textLines + ); + + if (showMediaTitle) { + addTextLine(getMediaTitle(cardOptions, item)); + } + + if (showOtherText) { + addOtherText( + cardOptions, + parentTitleUnderneath, + isOuterFooter, + item, + addTextLine, + serverId + ); + } + + if ( + (showTitle || !imgUrl) + && forceName + && overlayText + && textLines.length === 1 + ) { + textLines = []; + } + + if (overlayText && showTitle) { + textLines = [{ title: item.Name }]; + } + + return { + textLines + }; +} diff --git a/src/components/cardbuilder/Card/useCard.ts b/src/components/cardbuilder/Card/useCard.ts new file mode 100644 index 0000000000..5751471801 --- /dev/null +++ b/src/components/cardbuilder/Card/useCard.ts @@ -0,0 +1,121 @@ +import classNames from 'classnames'; +import useCardImageUrl from './useCardImageUrl'; +import { + resolveAction, + resolveMixedShapeByAspectRatio +} from '../cardBuilderUtils'; +import { getDataAttributes } from 'utils/items'; +import layoutManager from 'components/layoutManager'; + +import type { ItemDto } from 'types/itemDto'; +import type { CardOptions } from 'types/cardOptions'; + +interface UseCardProps { + item: ItemDto; + cardOptions: CardOptions; +} + +function useCard({ item, cardOptions }: UseCardProps) { + const action = resolveAction({ + defaultAction: cardOptions.action ?? 'link', + isFolder: item.IsFolder ?? false, + isPhoto: item.MediaType === 'Photo' + }); + + let shape = cardOptions.shape; + + if (shape === 'mixed') { + shape = resolveMixedShapeByAspectRatio(item.PrimaryImageAspectRatio); + } + + const imgInfo = useCardImageUrl({ + item: item.ProgramInfo ?? item, + cardOptions, + shape + }); + const imgUrl = imgInfo.imgUrl; + const blurhash = imgInfo.blurhash; + const forceName = imgInfo.forceName; + const coveredImage = cardOptions.coverImage ?? imgInfo.coverImage; + const overlayText = cardOptions.overlayText; + + const nameWithPrefix = item.SortName ?? item.Name ?? ''; + let prefix = nameWithPrefix.substring( + 0, + Math.min(3, nameWithPrefix.length) + ); + + if (prefix) { + prefix = prefix.toUpperCase(); + } + + const dataAttributes = getDataAttributes( + { + action, + itemServerId: item.ServerId ?? cardOptions.serverId, + context: cardOptions.context, + parentId: cardOptions.parentId, + collectionId: cardOptions.collectionId, + playlistId: cardOptions.playlistId, + itemId: item.Id, + itemTimerId: item.TimerId, + itemSeriesTimerId: item.SeriesTimerId, + itemChannelId: item.ChannelId, + itemType: item.Type, + itemMediaType: item.MediaType, + itemCollectionType: item.CollectionType, + itemIsFolder: item.IsFolder, + itemPath: item.Path, + itemStartDate: item.StartDate, + itemEndDate: item.EndDate, + itemUserData: item.UserData, + prefix + } + ); + + const cardClass = classNames( + 'card', + { [`${shape}Card`]: shape }, + cardOptions.cardCssClass, + cardOptions.cardClass, + { 'card-hoverable': layoutManager.desktop }, + { groupedCard: cardOptions.showChildCountIndicator && item.ChildCount }, + { + 'card-withuserdata': + item.Type !== 'MusicAlbum' + && item.Type !== 'MusicArtist' + && item.Type !== 'Audio' + }, + { itemAction: layoutManager.tv } + ); + + const cardBoxClass = classNames( + 'cardBox', + { visualCardBox: cardOptions.cardLayout }, + { 'cardBox-bottompadded': !cardOptions.cardLayout } + ); + + const getCardWrapperProps = () => ({ + className: cardClass, + dataAttributes + }); + + const getCardBoxProps = () => ({ + item, + cardOptions, + className: cardBoxClass, + shape, + imgUrl, + blurhash, + forceName, + coveredImage, + overlayText + }); + + return { + getCardWrapperProps, + getCardBoxProps + }; +} + +export default useCard; diff --git a/src/components/cardbuilder/Card/useCardImageUrl.ts b/src/components/cardbuilder/Card/useCardImageUrl.ts new file mode 100644 index 0000000000..ef1cb6ab61 --- /dev/null +++ b/src/components/cardbuilder/Card/useCardImageUrl.ts @@ -0,0 +1,301 @@ +import { BaseItemKind, ImageType } from '@jellyfin/sdk/lib/generated-client'; +import { useApi } from 'hooks/useApi'; +import { getDesiredAspect } from '../cardBuilderUtils'; + +import type { ItemDto } from 'types/itemDto'; +import type { CardOptions } from 'types/cardOptions'; + +type ShapeType = string | null | undefined; + +function getPreferThumbInfo(item: ItemDto, cardOptions: CardOptions) { + let imgType; + let itemId; + let imgTag; + let forceName = false; + + if (item.ImageTags?.Thumb) { + imgType = ImageType.Thumb; + imgTag = item.ImageTags.Thumb; + itemId = item.Id; + } else if (item.SeriesThumbImageTag && cardOptions.inheritThumb !== false) { + imgType = ImageType.Thumb; + imgTag = item.SeriesThumbImageTag; + itemId = item.SeriesId; + } else if ( + item.ParentThumbItemId + && cardOptions.inheritThumb !== false + && item.MediaType !== 'Photo' + ) { + imgType = ImageType.Thumb; + imgTag = item.ParentThumbImageTag; + itemId = item.ParentThumbItemId; + } else if (item.BackdropImageTags?.length) { + imgType = ImageType.Backdrop; + imgTag = item.BackdropImageTags[0]; + itemId = item.Id; + forceName = true; + } else if ( + item.ParentBackdropImageTags?.length + && cardOptions.inheritThumb !== false + && item.Type === BaseItemKind.Episode + ) { + imgType = ImageType.Backdrop; + imgTag = item.ParentBackdropImageTags[0]; + itemId = item.ParentBackdropItemId; + } + return { + itemId: itemId, + imgTag: imgTag, + imgType: imgType, + forceName: forceName + }; +} + +function getPreferLogoInfo(item: ItemDto) { + let imgType; + let itemId; + let imgTag; + + if (item.ImageTags?.Logo) { + imgType = ImageType.Logo; + imgTag = item.ImageTags.Logo; + itemId = item.Id; + } else if (item.ParentLogoImageTag && item.ParentLogoItemId) { + imgType = ImageType.Logo; + imgTag = item.ParentLogoImageTag; + itemId = item.ParentLogoItemId; + } + return { + itemId: itemId, + imgTag: imgTag, + imgType: imgType + }; +} + +function getCalculatedHeight( + width: number | undefined, + primaryImageAspectRatio: number | null | undefined +) { + if (width && primaryImageAspectRatio) { + return Math.round(width / primaryImageAspectRatio); + } +} + +function isForceName(cardOptions: CardOptions) { + return !!(cardOptions.preferThumb && cardOptions.showTitle !== false); +} + +function isCoverImage( + primaryImageAspectRatio: number | null | undefined, + uiAspect: number | null +) { + if (primaryImageAspectRatio && uiAspect) { + return Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect <= 0.2; + } + + return false; +} + +function shouldShowPreferBanner( + imageTagsBanner: string | undefined, + cardOptions: CardOptions, + shape: ShapeType +): boolean { + return ( + (cardOptions.preferBanner || shape === 'banner') + && Boolean(imageTagsBanner) + ); +} + +function shouldShowPreferDisc( + imageTagsDisc: string | undefined, + cardOptions: CardOptions +): boolean { + return cardOptions.preferDisc === true && Boolean(imageTagsDisc); +} + +function shouldShowImageTagsPrimary(item: ItemDto): boolean { + return ( + Boolean(item.ImageTags?.Primary) && (item.Type !== BaseItemKind.Episode || item.ChildCount !== 0) + ); +} + +function shouldShowImageTagsThumb(item: ItemDto): boolean { + return item.Type === BaseItemKind.Season && Boolean(item.ImageTags?.Thumb); +} + +function shouldShowSeriesThumbImageTag( + item: ItemDto, + cardOptions: CardOptions +): boolean { + return ( + Boolean(item.SeriesThumbImageTag) && cardOptions.inheritThumb !== false + ); +} + +function shouldShowParentThumbImageTag( + item: ItemDto, + cardOptions: CardOptions +): boolean { + return ( + Boolean(item.ParentThumbItemId) && cardOptions.inheritThumb !== false + ); +} + +function shouldShowParentBackdropImageTags(item: ItemDto): boolean { + return Boolean(item.AlbumId) && Boolean(item.AlbumPrimaryImageTag); +} + +function shouldShowPreferThumb(type: string | null | undefined, cardOptions: CardOptions): boolean { + return Boolean(cardOptions.preferThumb) && !(type === BaseItemKind.Program || type === BaseItemKind.Episode); +} + +function getCardImageInfo( + item: ItemDto, + cardOptions: CardOptions, + shape: ShapeType +) { + const width = cardOptions.width; + let height; + const primaryImageAspectRatio = item.PrimaryImageAspectRatio; + let forceName = false; + let imgTag; + let coverImage = false; + const uiAspect = getDesiredAspect(shape); + let imgType; + let itemId; + + if (shouldShowPreferThumb(item.Type, cardOptions)) { + const preferThumbInfo = getPreferThumbInfo(item, cardOptions); + imgType = preferThumbInfo.imgType; + imgTag = preferThumbInfo.imgTag; + itemId = preferThumbInfo.itemId; + forceName = preferThumbInfo.forceName; + } else if (shouldShowPreferBanner(item.ImageTags?.Banner, cardOptions, shape)) { + imgType = ImageType.Banner; + imgTag = item.ImageTags?.Banner; + itemId = item.Id; + } else if (shouldShowPreferDisc(item.ImageTags?.Disc, cardOptions)) { + imgType = ImageType.Disc; + imgTag = item.ImageTags?.Disc; + itemId = item.Id; + } else if (cardOptions.preferLogo) { + const preferLogoInfo = getPreferLogoInfo(item); + imgType = preferLogoInfo.imgType; + imgTag = preferLogoInfo.imgType; + itemId = preferLogoInfo.itemId; + } else if (shouldShowImageTagsPrimary(item)) { + imgType = ImageType.Primary; + imgTag = item.ImageTags?.Primary; + itemId = item.Id; + height = getCalculatedHeight(width, primaryImageAspectRatio); + forceName = isForceName(cardOptions); + coverImage = isCoverImage(primaryImageAspectRatio, uiAspect); + } else if (item.SeriesPrimaryImageTag) { + imgType = ImageType.Primary; + imgTag = item.SeriesPrimaryImageTag; + itemId = item.SeriesId; + } else if (item.PrimaryImageTag) { + imgType = ImageType.Primary; + imgTag = item.PrimaryImageTag; + itemId = item.PrimaryImageItemId; + height = getCalculatedHeight(width, primaryImageAspectRatio); + forceName = isForceName(cardOptions); + coverImage = isCoverImage(primaryImageAspectRatio, uiAspect); + } else if (item.ParentPrimaryImageTag) { + imgType = ImageType.Primary; + imgTag = item.ParentPrimaryImageTag; + itemId = item.ParentPrimaryImageItemId; + } else if (shouldShowParentBackdropImageTags(item)) { + imgType = ImageType.Primary; + imgTag = item.AlbumPrimaryImageTag; + itemId = item.AlbumId; + height = getCalculatedHeight(width, primaryImageAspectRatio); + forceName = isForceName(cardOptions); + coverImage = isCoverImage(primaryImageAspectRatio, uiAspect); + } else if (shouldShowImageTagsThumb(item)) { + imgType = ImageType.Thumb; + imgTag = item.ImageTags?.Thumb; + itemId = item.Id; + } else if (item.BackdropImageTags?.length) { + imgType = ImageType.Backdrop; + imgTag = item.BackdropImageTags[0]; + itemId = item.Id; + /*} else if (item.ImageTags?.Thumb) { + imgType = ImageType.Thumb; + imgTag = item.ImageTags.Thumb; + itemId = item.Id;*/ + } else if (shouldShowSeriesThumbImageTag(item, cardOptions)) { + imgType = ImageType.Thumb; + imgTag = item.SeriesThumbImageTag; + itemId = item.SeriesId; + } else if (shouldShowParentThumbImageTag(item, cardOptions)) { + imgType = ImageType.Thumb; + imgTag = item.ParentThumbImageTag; + itemId = item.ParentThumbItemId; + } else if ( + item.ParentBackdropImageTags?.length + && cardOptions.inheritThumb !== false + ) { + imgType = ImageType.Backdrop; + imgTag = item.ParentBackdropImageTags[0]; + itemId = item.ParentBackdropItemId; + } + + return { + imgType, + imgTag, + itemId, + width, + height, + forceName, + coverImage + }; +} + +interface UseCardImageUrlProps { + item: ItemDto; + cardOptions: CardOptions; + shape: ShapeType; +} + +function useCardImageUrl({ item, cardOptions, shape }: UseCardImageUrlProps) { + const { api } = useApi(); + const imgInfo = getCardImageInfo(item, cardOptions, shape); + + let width = imgInfo.width; + let height = imgInfo.height; + const imgTag = imgInfo.imgTag; + const imgType = imgInfo.imgType; + const itemId = imgInfo.itemId; + const ratio = window.devicePixelRatio || 1; + let imgUrl; + let blurhash; + + if (api && imgTag && imgType && itemId) { + if (width) { + width = Math.round(width * ratio); + } + + if (height) { + height = Math.round(height * ratio); + } + imgUrl = api?.getItemImageUrl(itemId, imgType, { + quality: 96, + fillWidth: width, + fillHeight: height, + tag: imgTag + }); + + blurhash = item?.ImageBlurHashes?.[imgType]?.[imgTag]; + } + + return { + imgUrl: imgUrl, + blurhash: blurhash, + forceName: imgInfo.forceName, + coverImage: imgInfo.coverImage + }; +} + +export default useCardImageUrl; diff --git a/src/components/cardbuilder/Card/useCardText.tsx b/src/components/cardbuilder/Card/useCardText.tsx new file mode 100644 index 0000000000..904777fe85 --- /dev/null +++ b/src/components/cardbuilder/Card/useCardText.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import Box from '@mui/material/Box'; +import classNames from 'classnames'; +import layoutManager from 'components/layoutManager'; +import CardText from './CardText'; +import { getCardTextLines } from './cardHelper'; + +import type { ItemDto } from 'types/itemDto'; +import type { CardOptions } from 'types/cardOptions'; + +const enableRightMargin = ( + isOuterFooter: boolean, + cardLayout: boolean | null | undefined, + centerText: boolean | undefined, + cardFooterAside: string | undefined +) => { + return ( + isOuterFooter + && cardLayout + && !centerText + && cardFooterAside !== 'none' + && layoutManager.mobile + ); +}; + +interface UseCardTextProps { + item: ItemDto; + cardOptions: CardOptions; + forceName: boolean; + overlayText: boolean | undefined; + imgUrl: string | undefined; + isOuterFooter: boolean; + cssClass: string; + forceLines: boolean; + maxLines: number | undefined; +} + +function useCardText({ + item, + cardOptions, + forceName, + imgUrl, + overlayText, + isOuterFooter, + cssClass, + forceLines, + maxLines +}: UseCardTextProps) { + const { textLines } = getCardTextLines({ + isOuterFooter, + overlayText, + forceName, + item, + cardOptions, + imgUrl + }); + + const addRightMargin = enableRightMargin( + isOuterFooter, + cardOptions.cardLayout, + cardOptions.centerText, + cardOptions.cardFooterAside + ); + + const renderCardTextLines = () => { + const components: React.ReactNode[] = []; + let valid = 0; + for (const textLine of textLines) { + const currentCssClass = classNames( + cssClass, + { + 'cardText-secondary': + valid > 0 && isOuterFooter + }, + { 'cardText-first': valid === 0 && isOuterFooter }, + { 'cardText-rightmargin': addRightMargin } + ); + + if (textLine) { + components.push( + + ); + + valid++; + if (maxLines && valid >= maxLines) { + break; + } + } + } + + if (forceLines) { + const linesLength = maxLines ?? Math.min(textLines.length, maxLines ?? textLines.length); + while (valid < linesLength) { + components.push( + +   + + ); + valid++; + } + } + + return components; + }; + + const cardTextLines = renderCardTextLines(); + + return { + cardTextLines + }; +} + +export default useCardText; diff --git a/src/components/cardbuilder/card.scss b/src/components/cardbuilder/card.scss index 28f55abe2d..731ef32a80 100644 --- a/src/components/cardbuilder/card.scss +++ b/src/components/cardbuilder/card.scss @@ -378,7 +378,7 @@ button::-moz-focus-inner { margin-right: 2em; } -.cardDefaultText { +.cardImageContainer > .cardDefaultText { white-space: normal; text-align: center; font-size: 2em; @@ -408,6 +408,7 @@ button::-moz-focus-inner { display: flex; align-items: center; contain: layout style; + z-index: 1; [dir="ltr"] & { right: 0.225em; @@ -852,7 +853,7 @@ button::-moz-focus-inner { opacity: 1; } -.cardOverlayFab-primary { +.cardOverlayContainer > .cardOverlayFab-primary { background-color: rgba(0, 0, 0, 0.7); font-size: 130%; padding: 0; @@ -865,7 +866,7 @@ button::-moz-focus-inner { left: 50%; } -.cardOverlayFab-primary:hover { +.cardOverlayContainer > .cardOverlayFab-primary:hover { transform: scale(1.4, 1.4); transition: 0.2s; } diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 811bac1851..acbe05c270 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -73,7 +73,7 @@ function getImageWidth(shape, screenWidth, isOrientationLandscape) { * @param {Object} items - A set of items. * @param {Object} options - Options for handling the items. */ -function setCardData(items, options) { +export function setCardData(items, options) { options.shape = options.shape || 'auto'; const primaryImageAspectRatio = imageLoader.getPrimaryImageAspectRatio(items); diff --git a/src/components/cardbuilder/cardBuilderUtils.ts b/src/components/cardbuilder/cardBuilderUtils.ts index d7215b190c..3ac471ccb1 100644 --- a/src/components/cardbuilder/cardBuilderUtils.ts +++ b/src/components/cardbuilder/cardBuilderUtils.ts @@ -10,10 +10,10 @@ const ASPECT_RATIOS = { /** * Determines if the item is live TV. - * @param {string} itemType - Item type to use for the check. + * @param {string | null | undefined} itemType - Item type to use for the check. * @returns {boolean} Flag showing if the item is live TV. */ -export const isUsingLiveTvNaming = (itemType: string): boolean => itemType === 'Program' || itemType === 'Timer' || itemType === 'Recording'; +export const isUsingLiveTvNaming = (itemType: string | null | undefined): boolean => itemType === 'Program' || itemType === 'Timer' || itemType === 'Recording'; /** * Resolves Card action to display diff --git a/src/types/cardOptions.ts b/src/types/cardOptions.ts index cb4f49c0af..3745d804a4 100644 --- a/src/types/cardOptions.ts +++ b/src/types/cardOptions.ts @@ -1,10 +1,12 @@ -import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; +import type { BaseItemDtoImageBlurHashes, BaseItemKind, ImageType, UserItemDataDto } from '@jellyfin/sdk/lib/generated-client'; import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; +import type { ItemDto, NullableString } from './itemDto'; +import { ParentId } from './library'; export interface CardOptions { itemsContainer?: HTMLElement | null; parentContainer?: HTMLElement | null; - items?: BaseItemDto[] | null; + items?: ItemDto[] | null; allowBottomPadding?: boolean; centerText?: boolean; coverImage?: boolean; @@ -12,13 +14,15 @@ export interface CardOptions { overlayMoreButton?: boolean; overlayPlayButton?: boolean; overlayText?: boolean; + imageBlurhashes?: BaseItemDtoImageBlurHashes | null; + preferBanner?: boolean; preferThumb?: boolean | string | null; preferDisc?: boolean; preferLogo?: boolean; scalable?: boolean; shape?: string | null; lazy?: boolean; - cardLayout?: boolean | string; + cardLayout?: boolean | null; showParentTitle?: boolean; showParentTitleOrTitle?: boolean; showAirTime?: boolean; @@ -37,7 +41,7 @@ export interface CardOptions { action?: string | null; defaultShape?: string; indexBy?: string; - parentId?: string | null; + parentId?: ParentId; showMenu?: boolean; cardCssClass?: string | null; cardClass?: string | null; @@ -61,9 +65,10 @@ export interface CardOptions { showSeriesTimerChannel?: boolean; showSongCount?: boolean; width?: number; + widths?: any; showChannelLogo?: boolean; showLogo?: boolean; - serverId?: string; + serverId?: NullableString; collectionId?: string | null; playlistId?: string | null; defaultCardImageIcon?: string; @@ -72,4 +77,46 @@ export interface CardOptions { showGroupCount?: boolean; containerClass?: string; noItemsMessage?: string; + showIndex?: boolean; + index?: string; + showIndexNumber?: boolean; + enableContentWrapper?: boolean; + enableOverview?: boolean; + enablePlayedButton?: boolean; + infoButton?: boolean; + imageSize?: string; + enableSideMediaInfo?: boolean; + imagePlayButton?: boolean; + border?: boolean; + highlight?: boolean; + smallIcon?: boolean; + artist?: boolean; + addToListButton?: boolean; + enableUserDataButtons?: boolean; + enableRatingButton?: boolean; + image?: boolean; + imageSource?: string; + showProgramDateTime?: boolean; + showChannel?: boolean; + mediaInfo?: boolean; + moreButton?: boolean; + recordButton?: boolean; + dragHandle?: boolean; + showProgramTime?: boolean; + parentTitleWithTitle?: boolean; + showIndexNumberLeft?: boolean; + sortBy?: string; + textLines?: (item: ItemDto) => (BaseItemKind | string | undefined)[]; + userData?: UserItemDataDto; + rightButtons?: { + icon: string; + title: string; + id: string; + }[]; + uiAspect?: number | null; + primaryImageAspectRatio?: number | null; + rows?: number | null; + imageType?: ImageType; + queryKey?: string[] } + From 31a77c25f3b8e793f7a7a02ad27427c35d4161e3 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 31 Jan 2024 04:32:54 +0300 Subject: [PATCH 08/90] Update favorite and played state to use Query Invalidation --- .../library/ProgramsSectionView.tsx | 7 ++- .../library/SuggestionsSectionView.tsx | 2 + .../emby-playstatebutton/PlayedButton.tsx | 35 +++++++---- .../emby-ratingbutton/FavoriteButton.tsx | 31 +++++---- src/hooks/useFetchItems.ts | 63 +++++++++---------- 5 files changed, 79 insertions(+), 59 deletions(-) diff --git a/src/apps/experimental/components/library/ProgramsSectionView.tsx b/src/apps/experimental/components/library/ProgramsSectionView.tsx index ac39d899b7..b15f319789 100644 --- a/src/apps/experimental/components/library/ProgramsSectionView.tsx +++ b/src/apps/experimental/components/library/ProgramsSectionView.tsx @@ -18,7 +18,7 @@ const ProgramsSectionView: FC = ({ sectionType, isUpcomingRecordingsEnabled = false }) => { - const { isLoading, data: sectionsWithItems } = useGetProgramsSectionsWithItems(parentId, sectionType); + const { isLoading, data: sectionsWithItems, refetch } = useGetProgramsSectionsWithItems(parentId, sectionType); const { isLoading: isUpcomingRecordingsLoading, data: upcomingRecordings @@ -60,8 +60,10 @@ const ProgramsSectionView: FC = ({ sectionTitle={globalize.translate(section.name)} items={items ?? []} url={getRouteUrl(section)} + reloadItems={refetch} cardOptions={{ - ...section.cardOptions + ...section.cardOptions, + queryKey: ['ProgramSectionWithItems'] }} /> @@ -73,6 +75,7 @@ const ProgramsSectionView: FC = ({ sectionTitle={group.name} items={group.timerInfo ?? []} cardOptions={{ + queryKey: ['Timers'], shape: 'overflowBackdrop', showTitle: true, showParentTitleOrTitle: true, diff --git a/src/apps/experimental/components/library/SuggestionsSectionView.tsx b/src/apps/experimental/components/library/SuggestionsSectionView.tsx index 039f49e4c6..d41270e4a6 100644 --- a/src/apps/experimental/components/library/SuggestionsSectionView.tsx +++ b/src/apps/experimental/components/library/SuggestionsSectionView.tsx @@ -102,6 +102,7 @@ const SuggestionsSectionView: FC = ({ url={getRouteUrl(section)} cardOptions={{ ...section.cardOptions, + queryKey: ['SuggestionSectionWithItems'], showTitle: true, centerText: true, cardLayout: false, @@ -117,6 +118,7 @@ const SuggestionsSectionView: FC = ({ sectionTitle={getRecommendationTittle(recommendation)} items={recommendation.Items ?? []} cardOptions={{ + queryKey: ['MovieRecommendations'], shape: 'overflowPortrait', showYear: true, scalable: true, diff --git a/src/elements/emby-playstatebutton/PlayedButton.tsx b/src/elements/emby-playstatebutton/PlayedButton.tsx index 89f4052b11..687953fb91 100644 --- a/src/elements/emby-playstatebutton/PlayedButton.tsx +++ b/src/elements/emby-playstatebutton/PlayedButton.tsx @@ -1,4 +1,5 @@ import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; +import { useQueryClient } from '@tanstack/react-query'; import React, { FC, useCallback } from 'react'; import CheckIcon from '@mui/icons-material/Check'; import { IconButton } from '@mui/material'; @@ -10,28 +11,30 @@ interface PlayedButtonProps { className?: string; isPlayed : boolean | undefined; itemId: string | null | undefined; - itemType: string | null | undefined + itemType: string | null | undefined, + queryKey?: string[] } const PlayedButton: FC = ({ className, isPlayed = false, itemId, - itemType + itemType, + queryKey }) => { + const queryClient = useQueryClient(); const { mutateAsync: togglePlayedMutation } = useTogglePlayedMutation(); - const [playedState, setPlayedState] = React.useState(isPlayed); const getTitle = useCallback(() => { let buttonTitle; if (itemType !== BaseItemKind.AudioBook) { - buttonTitle = playedState ? globalize.translate('Watched') : globalize.translate('MarkPlayed'); + buttonTitle = isPlayed ? globalize.translate('Watched') : globalize.translate('MarkPlayed'); } else { - buttonTitle = playedState ? globalize.translate('Played') : globalize.translate('MarkPlayed'); + buttonTitle = isPlayed ? globalize.translate('Played') : globalize.translate('MarkPlayed'); } return buttonTitle; - }, [playedState, itemType]); + }, [itemType, isPlayed]); const onClick = useCallback(async () => { try { @@ -39,23 +42,29 @@ const PlayedButton: FC = ({ throw new Error('Item has no Id'); } - const _isPlayed = await togglePlayedMutation({ + await togglePlayedMutation({ itemId, - playedState - }); - setPlayedState(!!_isPlayed); + isPlayed + }, + { onSuccess: async() => { + await queryClient.invalidateQueries({ + queryKey: queryKey, + type: 'all', + refetchType: 'active' + }); + } }); } catch (e) { console.error(e); } - }, [playedState, itemId, togglePlayedMutation]); + }, [itemId, togglePlayedMutation, isPlayed, queryClient, queryKey]); const btnClass = classNames( className, - { 'playstatebutton-played': playedState } + { 'playstatebutton-played': isPlayed } ); const iconClass = classNames( - { 'playstatebutton-icon-played': playedState } + { 'playstatebutton-icon-played': isPlayed } ); return ( = ({ className, isFavorite = false, - itemId + itemId, + queryKey }) => { + const queryClient = useQueryClient(); const { mutateAsync: toggleFavoriteMutation } = useToggleFavoriteMutation(); - const [favoriteState, setFavoriteState] = React.useState(isFavorite); const onClick = useCallback(async () => { try { @@ -25,28 +28,34 @@ const FavoriteButton: FC = ({ throw new Error('Item has no Id'); } - const _isFavorite = await toggleFavoriteMutation({ + await toggleFavoriteMutation({ itemId, - favoriteState - }); - setFavoriteState(!!_isFavorite); + isFavorite + }, + { onSuccess: async() => { + await queryClient.invalidateQueries({ + queryKey: queryKey, + type: 'all', + refetchType: 'active' + }); + } }); } catch (e) { console.error(e); } - }, [favoriteState, itemId, toggleFavoriteMutation]); + }, [isFavorite, itemId, queryClient, queryKey, toggleFavoriteMutation]); const btnClass = classNames( className, - { 'ratingbutton-withrating': favoriteState } + { 'ratingbutton-withrating': isFavorite } ); const iconClass = classNames( - { 'ratingbutton-icon-withrating': favoriteState } + { 'ratingbutton-icon-withrating': isFavorite } ); return ( fetchGetItemsViewByType( @@ -526,17 +528,17 @@ export const useGetGroupsUpcomingEpisodes = (parentId: ParentId) => { interface ToggleFavoriteMutationProp { itemId: string; - favoriteState: boolean + isFavorite: boolean } const fetchUpdateFavoriteStatus = async ( currentApi: JellyfinApiContext, itemId: string, - favoriteState: boolean + isFavorite: boolean ) => { const { api, user } = currentApi; if (api && user?.Id) { - if (favoriteState) { + if (isFavorite) { const response = await getUserLibraryApi(api).unmarkFavoriteItem({ userId: user.Id, itemId: itemId @@ -555,24 +557,24 @@ const fetchUpdateFavoriteStatus = async ( export const useToggleFavoriteMutation = () => { const currentApi = useApi(); return useMutation({ - mutationFn: ({ itemId, favoriteState }: ToggleFavoriteMutationProp) => - fetchUpdateFavoriteStatus(currentApi, itemId, favoriteState ) + mutationFn: ({ itemId, isFavorite }: ToggleFavoriteMutationProp) => + fetchUpdateFavoriteStatus(currentApi, itemId, isFavorite ) }); }; interface TogglePlayedMutationProp { itemId: string; - playedState: boolean + isPlayed: boolean } const fetchUpdatePlayedState = async ( currentApi: JellyfinApiContext, itemId: string, - playedState: boolean + isPlayed: boolean ) => { const { api, user } = currentApi; if (api && user?.Id) { - if (playedState) { + if (isPlayed) { const response = await getPlaystateApi(api).markUnplayedItem({ userId: user.Id, itemId: itemId @@ -591,8 +593,8 @@ const fetchUpdatePlayedState = async ( export const useTogglePlayedMutation = () => { const currentApi = useApi(); return useMutation({ - mutationFn: ({ itemId, playedState }: TogglePlayedMutationProp) => - fetchUpdatePlayedState(currentApi, itemId, playedState ) + mutationFn: ({ itemId, isPlayed }: TogglePlayedMutationProp) => + fetchUpdatePlayedState(currentApi, itemId, isPlayed ) }); }; @@ -676,7 +678,7 @@ const fetchGetTimers = async ( export const useGetTimers = (isUpcomingRecordingsEnabled: boolean, indexByDate?: boolean) => { const currentApi = useApi(); return useQuery({ - queryKey: ['Timers', isUpcomingRecordingsEnabled, indexByDate], + queryKey: ['Timers', { isUpcomingRecordingsEnabled, indexByDate }], queryFn: ({ signal }) => isUpcomingRecordingsEnabled ? fetchGetTimers(currentApi, indexByDate, { signal }) : [] }); @@ -830,7 +832,7 @@ const fetchGetSectionItems = async ( ], parentId: parentId ?? undefined, imageTypeLimit: 1, - enableImageTypes: [ImageType.Primary], + enableImageTypes: [ImageType.Primary, ImageType.Thumb], ...section.parametersOptions }, { @@ -882,19 +884,15 @@ const getSectionsWithItems = async ( const updatedSectionWithItems: SectionWithItems[] = []; for (const section of sections) { - try { - const items = await fetchGetSectionItems( - currentApi, parentId, section, options - ); + const items = await fetchGetSectionItems( + currentApi, parentId, section, options + ); - if (items && items.length > 0) { - updatedSectionWithItems.push({ - section, - items - }); - } - } catch (error) { - console.error(`Error occurred for section ${section.type}: ${error}`); + if (items && items.length > 0) { + updatedSectionWithItems.push({ + section, + items + }); } } @@ -908,7 +906,7 @@ export const useGetSuggestionSectionsWithItems = ( const currentApi = useApi(); const sections = getSuggestionSections(); return useQuery({ - queryKey: ['SuggestionSectionWithItems', suggestionSectionType], + queryKey: ['SuggestionSectionWithItems', { suggestionSectionType }], queryFn: ({ signal }) => getSectionsWithItems(currentApi, parentId, sections, suggestionSectionType, { signal }), enabled: !!parentId @@ -922,9 +920,8 @@ export const useGetProgramsSectionsWithItems = ( const currentApi = useApi(); const sections = getProgramSections(); return useQuery({ - queryKey: ['ProgramSectionWithItems', programSectionType], - queryFn: ({ signal }) => - getSectionsWithItems(currentApi, parentId, sections, programSectionType, { signal }) + queryKey: ['ProgramSectionWithItems', { programSectionType }], + queryFn: ({ signal }) => getSectionsWithItems(currentApi, parentId, sections, programSectionType, { signal }) + }); }; - From 4a12d5b2c659f12b0a3f1cfd805eac2eae96125d Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 31 Jan 2024 04:33:19 +0300 Subject: [PATCH 09/90] Replace card and list component in itemsView --- .../components/library/ItemsView.tsx | 73 +++++++++++-------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/src/apps/experimental/components/library/ItemsView.tsx b/src/apps/experimental/components/library/ItemsView.tsx index 65b26ffcdc..b13ab14165 100644 --- a/src/apps/experimental/components/library/ItemsView.tsx +++ b/src/apps/experimental/components/library/ItemsView.tsx @@ -8,10 +8,7 @@ import { useLocalStorage } from 'hooks/useLocalStorage'; import { useGetItem, useGetItemsViewByType } from 'hooks/useFetchItems'; import { getDefaultLibraryViewSettings, getSettingsKey } from 'utils/items'; import Loading from 'components/loading/LoadingComponent'; -import listview from 'components/listview/listview'; -import cardBuilder from 'components/cardbuilder/cardBuilder'; import { playbackManager } from 'components/playback/playbackmanager'; -import globalize from 'scripts/globalize'; import ItemsContainer from 'elements/emby-itemscontainer/ItemsContainer'; import AlphabetPicker from './AlphabetPicker'; import FilterButton from './filter/FilterButton'; @@ -22,12 +19,15 @@ import QueueButton from './QueueButton'; import ShuffleButton from './ShuffleButton'; import SortButton from './SortButton'; import GridListViewButton from './GridListViewButton'; -import { LibraryViewSettings, ParentId, ViewMode } from 'types/library'; +import NoItemsMessage from 'components/common/NoItemsMessage'; +import Lists from 'components/listview/List/Lists'; +import Cards from 'components/cardbuilder/Card/Cards'; +import { type LibraryViewSettings, type ParentId, ViewMode } from 'types/library'; import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; import { LibraryTab } from 'types/libraryTab'; -import { CardOptions } from 'types/cardOptions'; -import { ListOptions } from 'types/listOptions'; +import type { CardOptions } from 'types/cardOptions'; +import type { ListOptions } from 'types/listOptions'; interface ItemsViewProps { viewType: LibraryTab; @@ -135,9 +135,9 @@ const ItemsView: FC = ({ preferThumb: preferThumb, preferDisc: preferDisc, preferLogo: preferLogo, - overlayPlayButton: false, - overlayMoreButton: true, - overlayText: !libraryViewSettings.ShowTitle + overlayText: !libraryViewSettings.ShowTitle, + imageType: libraryViewSettings.ImageType, + queryKey: ['ItemsViewByType'] }; if ( @@ -146,20 +146,26 @@ const ItemsView: FC = ({ || viewType === LibraryTab.Episodes ) { cardOptions.showParentTitle = libraryViewSettings.ShowTitle; + cardOptions.overlayPlayButton = true; } else if (viewType === LibraryTab.Artists) { cardOptions.lines = 1; cardOptions.showYear = false; + cardOptions.overlayPlayButton = true; } else if (viewType === LibraryTab.Channels) { cardOptions.shape = 'square'; cardOptions.showDetailsMenu = true; cardOptions.showCurrentProgram = true; cardOptions.showCurrentProgramTime = true; } else if (viewType === LibraryTab.SeriesTimers) { - cardOptions.defaultShape = 'portrait'; - cardOptions.preferThumb = 'auto'; + cardOptions.shape = 'backdrop'; cardOptions.showSeriesTimerTime = true; cardOptions.showSeriesTimerChannel = true; + cardOptions.overlayMoreButton = true; cardOptions.lines = 3; + } else if (viewType === LibraryTab.Movies) { + cardOptions.overlayPlayButton = true; + } else if (viewType === LibraryTab.Series || viewType === LibraryTab.Networks) { + cardOptions.overlayMoreButton = true; } return cardOptions; @@ -172,27 +178,32 @@ const ItemsView: FC = ({ viewType ]); - const getItemsHtml = useCallback(() => { - let html = ''; + const getItems = useCallback(() => { + if (!itemsResult?.Items?.length) { + return ; + } if (libraryViewSettings.ViewMode === ViewMode.ListView) { - html = listview.getListViewHtml(getListOptions()); - } else { - html = cardBuilder.getCardsHtml( - itemsResult?.Items ?? [], - getCardOptions() + return ( + ); } - - if (!itemsResult?.Items?.length) { - html += '
'; - html += '

' + globalize.translate('MessageNothingHere') + '

'; - html += '

' + globalize.translate(noItemsMessage) + '

'; - html += '
'; - } - - return html; - }, [libraryViewSettings.ViewMode, itemsResult?.Items, getListOptions, getCardOptions, noItemsMessage]); + return ( + + ); + }, [ + libraryViewSettings.ViewMode, + itemsResult?.Items, + getListOptions, + getCardOptions, + noItemsMessage + ]); const totalRecordCount = itemsResult?.TotalRecordCount ?? 0; const items = itemsResult?.Items ?? []; @@ -289,8 +300,10 @@ const ItemsView: FC = ({ className={itemsContainerClass} parentId={parentId} reloadItems={refetch} - getItemsHtml={getItemsHtml} - /> + queryKey={['ItemsViewByType']} + > + {getItems()} + )} {isPaginationEnabled && ( From 42b4d08e55ece6b068539a2d689ff40039e41f06 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 31 Jan 2024 04:34:00 +0300 Subject: [PATCH 10/90] Replace card component in SectionContainer --- .../components/library/SectionContainer.tsx | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/src/apps/experimental/components/library/SectionContainer.tsx b/src/apps/experimental/components/library/SectionContainer.tsx index 18669452a0..8eb64c4e6f 100644 --- a/src/apps/experimental/components/library/SectionContainer.tsx +++ b/src/apps/experimental/components/library/SectionContainer.tsx @@ -1,43 +1,29 @@ import type { BaseItemDto, TimerInfoDto } from '@jellyfin/sdk/lib/generated-client'; -import React, { FC, useEffect, useRef } from 'react'; +import React, { FC } from 'react'; -import cardBuilder from 'components/cardbuilder/cardBuilder'; import ItemsContainer from 'elements/emby-itemscontainer/ItemsContainer'; import Scroller from 'elements/emby-scroller/Scroller'; import LinkButton from 'elements/emby-button/LinkButton'; -import imageLoader from 'components/images/imageLoader'; - -import { CardOptions } from 'types/cardOptions'; +import Cards from 'components/cardbuilder/Card/Cards'; +import type { CardOptions } from 'types/cardOptions'; interface SectionContainerProps { url?: string; sectionTitle: string; items: BaseItemDto[] | TimerInfoDto[]; cardOptions: CardOptions; + reloadItems?: () => void; } const SectionContainer: FC = ({ sectionTitle, url, items, - cardOptions + cardOptions, + reloadItems }) => { - const element = useRef(null); - - useEffect(() => { - const itemsContainer = element.current?.querySelector('.itemsContainer'); - cardBuilder.buildCards(items, { - itemsContainer: itemsContainer, - parentContainer: element.current, - - ...cardOptions - }); - - imageLoader.lazyChildren(itemsContainer); - }, [cardOptions, items]); - return ( -
+
{url && items.length > 5 ? ( = ({ > + reloadItems={reloadItems} + queryKey={cardOptions.queryKey} + > + +
); From e3b618f2fb7113ed3c4f49bac29607bc1e9897ce Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 31 Jan 2024 04:36:00 +0300 Subject: [PATCH 11/90] Refactor ItemsContainer invalidate Queries --- .../emby-itemscontainer/ItemsContainer.tsx | 187 ++++-------------- 1 file changed, 43 insertions(+), 144 deletions(-) diff --git a/src/elements/emby-itemscontainer/ItemsContainer.tsx b/src/elements/emby-itemscontainer/ItemsContainer.tsx index bcbda52841..f817221b0c 100644 --- a/src/elements/emby-itemscontainer/ItemsContainer.tsx +++ b/src/elements/emby-itemscontainer/ItemsContainer.tsx @@ -1,13 +1,11 @@ import type { - LibraryUpdateInfo, - SeriesTimerInfoDto, - TimerInfoDto, - UserItemDataDto + LibraryUpdateInfo } from '@jellyfin/sdk/lib/generated-client'; import React, { FC, useCallback, useEffect, useRef } from 'react'; import classNames from 'classnames'; -import { Box } from '@mui/material'; +import Box from '@mui/material/Box'; import Sortable from 'sortablejs'; +import { useQueryClient } from '@tanstack/react-query'; import { usePlaylistsMoveItemMutation } from 'hooks/useFetchItems'; import Events, { Event } from 'utils/events'; import serverNotifications from 'scripts/serverNotifications'; @@ -40,11 +38,11 @@ interface ItemsContainerProps { isContextMenuEnabled?: boolean; isMultiSelectEnabled?: boolean; isDragreOrderEnabled?: boolean; - dataMonitor?: string; + eventsToMonitor?: string[]; parentId?: ParentId; reloadItems?: () => void; getItemsHtml?: () => string; - children?: React.ReactNode; + queryKey?: string[] } const ItemsContainer: FC = ({ @@ -52,12 +50,14 @@ const ItemsContainer: FC = ({ isContextMenuEnabled, isMultiSelectEnabled, isDragreOrderEnabled, - dataMonitor, + eventsToMonitor = [], parentId, + queryKey, reloadItems, getItemsHtml, children }) => { + const queryClient = useQueryClient(); const { mutateAsync: playlistsMoveItemMutation } = usePlaylistsMoveItemMutation(); const itemsContainerRef = useRef(null); const multiSelectref = useRef(null); @@ -172,6 +172,14 @@ const ItemsContainer: FC = ({ } }, []); + const invalidateQueries = useCallback(async () => { + await queryClient.invalidateQueries({ + queryKey: queryKey, + type: 'all', + refetchType: 'active' + }); + }, [queryClient, queryKey]); + const notifyRefreshNeeded = useCallback( (isInForeground: boolean) => { if (!reloadItems) return; @@ -184,144 +192,37 @@ const ItemsContainer: FC = ({ [reloadItems] ); - const getEventsToMonitor = useCallback(() => { - const monitor = dataMonitor; - if (monitor) { - return monitor.split(','); - } - - return []; - }, [dataMonitor]); - - const onUserDataChanged = useCallback( - (_e: Event, userData: UserItemDataDto) => { - const itemsContainer = itemsContainerRef.current as HTMLDivElement; - - import('../../components/cardbuilder/cardBuilder') - .then((cardBuilder) => { - cardBuilder.onUserDataChanged(userData, itemsContainer); - }) - .catch((err) => { - console.error( - '[onUserDataChanged] failed to load onUserData Changed', - err - ); - }); - - const eventsToMonitor = getEventsToMonitor(); - if ( - eventsToMonitor.indexOf('markfavorite') !== -1 - || eventsToMonitor.indexOf('markplayed') !== -1 - ) { - notifyRefreshNeeded(false); - } - }, - [getEventsToMonitor, notifyRefreshNeeded] + const onUserDataChanged = useCallback(async () => { + await invalidateQueries(); + }, + [invalidateQueries] ); - const onTimerCreated = useCallback( - (_e: Event, data: TimerInfoDto) => { - const itemsContainer = itemsContainerRef.current as HTMLDivElement; - const eventsToMonitor = getEventsToMonitor(); - if (eventsToMonitor.indexOf('timers') !== -1) { - notifyRefreshNeeded(false); - return; - } - - const programId = data.ProgramId; - // This could be null, not supported by all tv providers - const newTimerId = data.Id; - if (programId && newTimerId) { - import('../../components/cardbuilder/cardBuilder') - .then((cardBuilder) => { - cardBuilder.onTimerCreated( - programId, - newTimerId, - itemsContainer - ); - }) - .catch((err) => { - console.error( - '[onTimerCreated] failed to load onTimer Created', - err - ); - }); - } - }, - [getEventsToMonitor, notifyRefreshNeeded] + const onTimerCreated = useCallback(async () => { + await invalidateQueries(); + }, + [invalidateQueries] ); - const onSeriesTimerCreated = useCallback(() => { - const eventsToMonitor = getEventsToMonitor(); - if (eventsToMonitor.indexOf('seriestimers') !== -1) { - notifyRefreshNeeded(false); - } - }, [getEventsToMonitor, notifyRefreshNeeded]); + const onSeriesTimerCreated = useCallback(async () => { + await invalidateQueries(); + }, [invalidateQueries]); - const onTimerCancelled = useCallback( - (_e: Event, data: TimerInfoDto) => { - const itemsContainer = itemsContainerRef.current as HTMLDivElement; - const eventsToMonitor = getEventsToMonitor(); - if (eventsToMonitor.indexOf('timers') !== -1) { - notifyRefreshNeeded(false); - return; - } - - const timerId = data.Id; - - if (timerId) { - import('../../components/cardbuilder/cardBuilder') - .then((cardBuilder) => { - cardBuilder.onTimerCancelled(timerId, itemsContainer); - }) - .catch((err) => { - console.error( - '[onTimerCancelled] failed to load onTimer Cancelled', - err - ); - }); - } - }, - [getEventsToMonitor, notifyRefreshNeeded] + const onTimerCancelled = useCallback(async () => { + await invalidateQueries(); + }, + [invalidateQueries] ); - const onSeriesTimerCancelled = useCallback( - (_e: Event, data: SeriesTimerInfoDto) => { - const itemsContainer = itemsContainerRef.current as HTMLDivElement; - const eventsToMonitor = getEventsToMonitor(); - if (eventsToMonitor.indexOf('seriestimers') !== -1) { - notifyRefreshNeeded(false); - return; - } - - const cancelledTimerId = data.Id; - - if (cancelledTimerId) { - import('../../components/cardbuilder/cardBuilder') - .then((cardBuilder) => { - cardBuilder.onSeriesTimerCancelled( - cancelledTimerId, - itemsContainer - ); - }) - .catch((err) => { - console.error( - '[onSeriesTimerCancelled] failed to load onSeriesTimer Cancelled', - err - ); - }); - } - }, - [getEventsToMonitor, notifyRefreshNeeded] + const onSeriesTimerCancelled = useCallback(async () => { + await invalidateQueries(); + }, + [invalidateQueries] ); const onLibraryChanged = useCallback( - (_e: Event, data: LibraryUpdateInfo) => { - const eventsToMonitor = getEventsToMonitor(); - if ( - eventsToMonitor.indexOf('seriestimers') !== -1 - || eventsToMonitor.indexOf('timers') !== -1 - ) { + (_e: Event, apiClient, data: LibraryUpdateInfo) => { + if (eventsToMonitor.includes('seriestimers') || eventsToMonitor.includes('timers')) { // yes this is an assumption return; } @@ -348,32 +249,31 @@ const ItemsContainer: FC = ({ notifyRefreshNeeded(false); }, - [getEventsToMonitor, notifyRefreshNeeded, parentId] + [eventsToMonitor, notifyRefreshNeeded, parentId] ); const onPlaybackStopped = useCallback( - (_e: Event, stopInfo) => { + (_e: Event, apiClient, stopInfo) => { const state = stopInfo.state; - const eventsToMonitor = getEventsToMonitor(); if ( state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video' ) { - if (eventsToMonitor.indexOf('videoplayback') !== -1) { + if (eventsToMonitor.includes('videoplayback')) { notifyRefreshNeeded(true); return; } } else if ( state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Audio' - && eventsToMonitor.indexOf('audioplayback') !== -1 + && eventsToMonitor.includes('videoplayback') ) { notifyRefreshNeeded(true); return; } }, - [getEventsToMonitor, notifyRefreshNeeded] + [eventsToMonitor, notifyRefreshNeeded] ); const setFocus = useCallback( @@ -418,10 +318,9 @@ const ItemsContainer: FC = ({ if (getItemsHtml) { itemsContainer.innerHTML = getItemsHtml(); + imageLoader.lazyChildren(itemsContainer); } - imageLoader.lazyChildren(itemsContainer); - if (hasActiveElement) { setFocus(itemsContainer, focusId); } From 876fbee53edeb9dab2dc3684da0289ad87fc055b Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 31 Jan 2024 05:25:38 +0300 Subject: [PATCH 12/90] Fix navigation for mobile layout --- .../cardbuilder/Card/CardOverlayButtons.tsx | 17 ++++++++++++----- src/types/dataAttributes.ts | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/components/cardbuilder/Card/CardOverlayButtons.tsx b/src/components/cardbuilder/Card/CardOverlayButtons.tsx index 0ac8463969..c2938c1088 100644 --- a/src/components/cardbuilder/Card/CardOverlayButtons.tsx +++ b/src/components/cardbuilder/Card/CardOverlayButtons.tsx @@ -1,8 +1,8 @@ import React, { FC } from 'react'; -import Box from '@mui/material/Box'; import ButtonGroup from '@mui/material/ButtonGroup'; import classNames from 'classnames'; - +import { appRouter } from 'components/router/appRouter'; +import escapeHTML from 'escape-html'; import PlayArrowIconButton from '../../common/PlayArrowIconButton'; import MoreVertIconButton from '../../common/MoreVertIconButton'; @@ -43,6 +43,10 @@ const CardOverlayButtons: FC = ({ overlayPlayButton = item.MediaType === 'Video'; } + const url = appRouter.getRouteUrl(item, { + parentId: cardOptions.parentId + }); + const btnCssClass = classNames( 'paper-icon-button-light', 'cardOverlayButton', @@ -55,8 +59,10 @@ const CardOverlayButtons: FC = ({ ); return ( - = ({ borderRadius: '0.2em' }} > + {cardOptions.centerPlayButton && ( = ({ /> )} - + ); }; diff --git a/src/types/dataAttributes.ts b/src/types/dataAttributes.ts index 6e91b125cd..1258d61cd4 100644 --- a/src/types/dataAttributes.ts +++ b/src/types/dataAttributes.ts @@ -2,7 +2,7 @@ import type { CollectionType, UserItemDataDto } from '@jellyfin/sdk/lib/generate import type { NullableBoolean, NullableNumber, NullableString } from './itemDto'; export type AttributesOpts = { - context?: CollectionType | undefined, + context?: CollectionType, parentId?: NullableString, collectionId?: NullableString, playlistId?: NullableString, From ed46ee5254bc57f96e837d9dc0c7324a98f8c360 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Thu, 1 Feb 2024 19:41:08 +0300 Subject: [PATCH 13/90] Replace deprecated getItemImageUrl with imageUrlsApi.getItemImageUrlById --- .../cardbuilder/Card/CardContent.tsx | 2 +- src/components/cardbuilder/Card/cardHelper.ts | 3 +- .../cardbuilder/Card/useCardImageUrl.ts | 51 +++++++++---------- src/components/listview/List/listHelper.ts | 3 +- 4 files changed, 28 insertions(+), 31 deletions(-) diff --git a/src/components/cardbuilder/Card/CardContent.tsx b/src/components/cardbuilder/Card/CardContent.tsx index 3d846758a4..11a443fb90 100644 --- a/src/components/cardbuilder/Card/CardContent.tsx +++ b/src/components/cardbuilder/Card/CardContent.tsx @@ -32,7 +32,7 @@ const CardContent: FC = ({ return (
Date: Tue, 6 Feb 2024 01:06:34 +0800 Subject: [PATCH 14/90] Relax HEVC on Safari for hvc1/dvh1 direct-play Signed-off-by: nyanmisaka --- src/scripts/browserDeviceProfile.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/scripts/browserDeviceProfile.js b/src/scripts/browserDeviceProfile.js index 1f40349af4..aef1ca0050 100644 --- a/src/scripts/browserDeviceProfile.js +++ b/src/scripts/browserDeviceProfile.js @@ -583,11 +583,7 @@ export default function (options) { } if (canPlayHevc(videoTestElement, options)) { - // safari is lying on HDR and 60fps videos, use fMP4 instead - if (!browser.safari) { - mp4VideoCodecs.push('hevc'); - } - + mp4VideoCodecs.push('hevc'); if (browser.tizen || browser.web0s) { hlsInTsVideoCodecs.push('hevc'); } @@ -1098,6 +1094,25 @@ export default function (options) { }); } + // Safari quirks for HEVC direct-play + if (browser.safari) { + // Only hvc1 & dvh1 tags are supported + hevcCodecProfileConditions.push({ + Condition: 'EqualsAny', + Property: 'VideoCodecTag', + Value: 'hvc1|dvh1', + IsRequired: true + }); + + // Framerate above 60fps is not supported + hevcCodecProfileConditions.push({ + Condition: 'LessThanEqual', + Property: 'VideoFramerate', + Value: '60', + IsRequired: true + }); + } + // On iOS 12.x, for TS container max h264 level is 4.2 if (browser.iOS && browser.iOSVersion < 13) { const codecProfile = { From c7f925a9c621a2d6be7b954dcc3545943d065747 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Tue, 6 Feb 2024 01:07:47 +0800 Subject: [PATCH 15/90] Enable client side tone-mapping on EdgeChromium 121+ Signed-off-by: nyanmisaka --- src/scripts/browserDeviceProfile.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scripts/browserDeviceProfile.js b/src/scripts/browserDeviceProfile.js index aef1ca0050..2cdc0758bc 100644 --- a/src/scripts/browserDeviceProfile.js +++ b/src/scripts/browserDeviceProfile.js @@ -194,7 +194,8 @@ function supportsHdr10(options) { || browser.web0s || browser.safari && ((browser.iOS && browser.iOSVersion >= 11) || browser.osx) // Chrome mobile and Firefox have no client side tone-mapping - // Edge Chromium on Nvidia is known to have color issues on 10-bit video + // Edge Chromium 121+ fixed the tone-mapping color issue on Nvidia + || browser.edgeChromium && (browser.versionMajor >= 121) || browser.chrome && !browser.mobile ); } From 36c19ec39afb4ba9cededc63dadb9715c6b6ebaf Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Mon, 19 Feb 2024 05:59:54 +0300 Subject: [PATCH 16/90] Fix mediaSourceCount display --- src/components/indicators/useIndicator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/indicators/useIndicator.tsx b/src/components/indicators/useIndicator.tsx index 3015094b14..214c736267 100644 --- a/src/components/indicators/useIndicator.tsx +++ b/src/components/indicators/useIndicator.tsx @@ -63,7 +63,7 @@ const useIndicator = (item: ItemDto) => { const getMediaSourceIndicator = () => { const mediaSourceCount = item.MediaSourceCount ?? 0; if (mediaSourceCount > 1) { - return mediaSourceCount; + return {mediaSourceCount}; } return null; From f7e237495768b63852cc34f5d80c4c8cb6c6debb Mon Sep 17 00:00:00 2001 From: Saleh Date: Tue, 27 Feb 2024 06:59:34 +0000 Subject: [PATCH 17/90] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ar/ --- src/strings/ar.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/ar.json b/src/strings/ar.json index f46da16a21..b4f60f92e4 100644 --- a/src/strings/ar.json +++ b/src/strings/ar.json @@ -1722,5 +1722,6 @@ "LabelEnableAudioVbrHelp": "معدل البِت المتغير ينتج على جودة أفضل مقارنة بمعدل البت المتوسط، ولكن في بعض الحالات النادرة قد يسبب مشاكل في التخزين المؤقت والتوافق.", "LabelSegmentKeepSecondsHelp": "الزمن بالثواني الذي يجب الاحتفاظ به للشرائح قبل أن يتم الكتابة فوقها. يجب أن يكون أكبر من \"بعد الخنق\". يعمل هذا ألأعداد فقط إذا كان حذف الشرائح مفعلًا.", "AiTranslated": "مترجمة من قبل ذكاء اسطناعي", - "SelectAudioNormalizationHelp": "كسب الالبوم-تعديل الصوت لكل مسار لكي يعملون بنفس مستوى- كسب الالبوم- تعديل مستوى الصوت لكل المسارات في البوم واحد مع ابقاء على النطاق الديناميكي للألبوم." + "SelectAudioNormalizationHelp": "كسب الالبوم-تعديل الصوت لكل مسار لكي يعملون بنفس مستوى- كسب الالبوم- تعديل مستوى الصوت لكل المسارات في البوم واحد مع ابقاء على النطاق الديناميكي للألبوم.", + "ButtonEditUser": "تعديل مستخدم" } From a9e67d28040bd04c305feef7e6cdd9d5df35ebab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Tue, 27 Feb 2024 06:56:28 +0000 Subject: [PATCH 18/90] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/cs/ --- src/strings/cs.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/cs.json b/src/strings/cs.json index bcb9f12675..a9ef202943 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -1785,5 +1785,6 @@ "HeaderAllRecordings": "Všechny nahrávky", "LabelBuildVersion": "Verze sestavení", "LabelServerVersion": "Verze serveru", - "LabelWebVersion": "Verze webu" + "LabelWebVersion": "Verze webu", + "ButtonEditUser": "Upravit uživatele" } From 56e9691e659fb96ab4238ff351ce6e505261e862 Mon Sep 17 00:00:00 2001 From: limofij minhlun Date: Tue, 27 Feb 2024 09:41:16 +0000 Subject: [PATCH 19/90] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/it/ --- src/strings/it.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/strings/it.json b/src/strings/it.json index 4dddc27c07..f983f90c5c 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -53,7 +53,7 @@ "ButtonBack": "Indietro", "ButtonCancel": "Annulla", "ButtonChangeServer": "Cambia Server", - "ButtonEditOtherUserPreferences": "Modifica questo utente di profilo, l'immagine e le preferenze personali.", + "ButtonEditOtherUserPreferences": "Modifica il profilo, l'immagine e le preferenze personali di questo utente.", "ButtonForgotPassword": "Password Dimenticata", "ButtonFullscreen": "Schermo Intero", "ButtonGotIt": "Ho capito", @@ -74,16 +74,16 @@ "ButtonResume": "Riprendi", "ButtonRevoke": "Revoca", "ButtonScanAllLibraries": "Scansiona Tutte le Librerie", - "ButtonSelectDirectory": "Seleziona cartella", + "ButtonSelectDirectory": "Seleziona Cartella", "ButtonSelectView": "Seleziona vista", "ButtonSend": "Invia", "ButtonShutdown": "Arresta Server", "ButtonSignIn": "Accedi", "ButtonSignOut": "Esci", - "ButtonStart": "Avvio", + "ButtonStart": "Avvia", "ButtonSubmit": "Invia", "ButtonUninstall": "Disinstalla", - "ButtonWebsite": "Web", + "ButtonWebsite": "Sito Web", "CancelRecording": "Annulla la registrazione", "CancelSeries": "Annulla Serie TV", "Categories": "Categorie", @@ -1114,7 +1114,7 @@ "Box": "Scatola", "ButtonInfo": "Info", "ButtonOk": "Ok", - "ButtonStop": "Stop", + "ButtonStop": "Ferma", "ButtonTrailer": "Trailer", "ChangingMetadataImageSettingsNewContent": "I cambiamenti alle impostazioni dei download dei metadati verranno applicati solamente ai nuovi contenuti aggiunti alla libreria. Per applicare i cambiamenti ai titoli già esistenti devi ricaricare i metadati manualmente.", "DownloadsValue": "{0} scaricati", @@ -1785,5 +1785,6 @@ "HeaderAllRecordings": "Tutte le registrazioni", "LabelBuildVersion": "Versione Compilata", "LabelServerVersion": "Versione server", - "LabelWebVersion": "Versione web" + "LabelWebVersion": "Versione web", + "ButtonEditUser": "Modifica utente" } From 0ce689bedcd05316dddf7ec4772bb1b2807fe873 Mon Sep 17 00:00:00 2001 From: ChiefJReloaded Date: Tue, 27 Feb 2024 12:30:23 +0000 Subject: [PATCH 20/90] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/de.json b/src/strings/de.json index f9bab17e3e..3931081f24 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1785,5 +1785,6 @@ "HeaderAllRecordings": "Alle Aufnahmen", "LabelBuildVersion": "Build-Version", "LabelServerVersion": "Server-Version", - "LabelWebVersion": "Web-Version" + "LabelWebVersion": "Web-Version", + "ButtonEditUser": "Editiere Benutzer" } From 91760d2040f5eeb46629731a11504480532dd2de Mon Sep 17 00:00:00 2001 From: ChiefJReloaded Date: Tue, 27 Feb 2024 14:01:06 +0000 Subject: [PATCH 21/90] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strings/de.json b/src/strings/de.json index 3931081f24..3d125d9048 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1761,7 +1761,7 @@ "LabelThrottleDelaySeconds": "Drosseln nach", "LabelThrottleDelaySecondsHelp": "Zeit in Sekunden, nach der die Transkodierung gedrosselt wird. Muss groß genug sein, damit der Client einen gesunden Puffer aufrechterhalten kann. Funktioniert nur wenn \"Transkodierung drosseln\" aktiviert ist.", "LabelSegmentKeepSeconds": "Zeit, wie lange Segmente behalten werden", - "LabelSegmentKeepSecondsHelp": "Zeit in Sekunden, in der Segmente nicht überschrieben werden dürfen. Muss größer sein als \"Drosseln nach\". Funktioniert nur wenn \"Segmente löschen\" aktiviert ist.", + "LabelSegmentKeepSecondsHelp": "Zeit in Sekunden, für die Segmente behalten werden sollen, bevor sie überschrieben werden. Muss größer sein als \"Drosseln nach\". Funktioniert nur wenn \"Segmente löschen\" aktiviert ist.", "LogoScreensaver": "Logo Bildschirmschoner", "UnknownError": "Ein unbekannter Fehler trat auf.", "GridView": "Kachelansicht", @@ -1779,7 +1779,7 @@ "LabelBackdropScreensaverIntervalHelp": "Die Zeit in Sekunden zwischen dem Wechsel verschiedener Hintergrundbilder im Bildschirmschoner.", "SearchResultsEmpty": "Entschuldigung! Es konnten keine Ergebnisse für „{0}“ gefunden werden", "LabelTrackGain": "Titel Gain", - "SelectAudioNormalizationHelp": "Track Gain - passt die Lautstärke der einzelnen Tracks an, so dass sie mit der gleichen Lautstärke wiedergegeben werden. Albumverstärkung - passt die Lautstärke aller Titel eines Albums an, wobei der Dynamikbereich des Albums erhalten bleibt.", + "SelectAudioNormalizationHelp": "Track Gain - passt die Lautstärke der einzelnen Tracks an, sodass sie mit der gleichen Lautstärke wiedergegeben werden. Albumverstärkung - passt die Lautstärke aller Titel eines Albums an, wobei der Dynamikbereich des Albums erhalten bleibt.", "LabelAlbumGain": "Albumlautstärke", "LabelSelectAudioNormalization": "Audio Normalisierung", "HeaderAllRecordings": "Alle Aufnahmen", From 713ac0e130bfd452debf1c2a1b25a64b5bcaa01f Mon Sep 17 00:00:00 2001 From: stanol Date: Tue, 27 Feb 2024 13:40:32 +0000 Subject: [PATCH 22/90] Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/uk/ --- src/strings/uk.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/uk.json b/src/strings/uk.json index a1df7529f8..241873c27a 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -1782,5 +1782,6 @@ "HeaderAllRecordings": "Всі записи", "LabelBuildVersion": "Версія збірки", "LabelServerVersion": "Версія сервера", - "LabelWebVersion": "Версія вебу" + "LabelWebVersion": "Версія вебу", + "ButtonEditUser": "Редагувати користувача" } From b74a43d64d71e2f675553bb1b9f74ec544534582 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 27 Feb 2024 13:29:10 -0500 Subject: [PATCH 23/90] Add commit sha as JF version in CI builds --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d1f0f5626d..d90e98140f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,6 +31,8 @@ jobs: run: npm ci --no-audit - name: Run a production build + env: + JELLYFIN_VERSION: ${{ github.sha }} run: npm run build:production - name: Update config.json for testing From 088c5e88d69f8865a21f8d3194ad49ee0150e373 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 27 Feb 2024 15:47:56 -0500 Subject: [PATCH 24/90] Fix commit sha values in actions --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d90e98140f..2594cf74fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: - name: Run a production build env: - JELLYFIN_VERSION: ${{ github.sha }} + JELLYFIN_VERSION: ${{ github.event.pull_request.head.sha || github.sha }} run: npm run build:production - name: Update config.json for testing @@ -58,7 +58,7 @@ jobs: - name: Save PR context env: PR_NUMBER: ${{ github.event.number }} - PR_SHA: ${{ github.sha }} + PR_SHA: ${{ github.event.pull_request.head.sha }} run: | echo $PR_NUMBER > PR_number echo $PR_SHA > PR_sha From 037cdeb3e6baf0d6be269a602e21cf7c27c54eff Mon Sep 17 00:00:00 2001 From: DJSweder Date: Wed, 28 Feb 2024 16:19:31 +0000 Subject: [PATCH 25/90] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/cs/ --- src/strings/cs.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strings/cs.json b/src/strings/cs.json index a9ef202943..e5f0df0e76 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -1318,8 +1318,8 @@ "LabelSyncPlayTimeOffset": "Časový rozdíl mezi serverem:", "HeaderSyncPlayEnabled": "Synchronizace přehrávání povolena", "HeaderSyncPlaySelectGroup": "Připojit ke skupině", - "EnableDetailsBannerHelp": "Zobrazí obrázek ve vrchní části detailu položky.", - "EnableDetailsBanner": "Obrázek detailu", + "EnableDetailsBannerHelp": "Zobrazí banner v horní části stránky s detailem položky.", + "EnableDetailsBanner": "Zobrazit banner na stránce s detailem", "ShowMore": "Zobrazit více", "ShowLess": "Zobrazit méně", "EnableBlurHashHelp": "Obrázky, které se ještě načítají, budou zobrazeny pomocí jedinečných zástupných obrázků.", From 076a5b4e2c6f46c022df76c6716d656f2c416d58 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 29 Feb 2024 01:05:54 +0800 Subject: [PATCH 26/90] Enable AV1 remuxing via HLS fMP4 on Safari AV1 should be supported by Apple devices that support hardware acceleration. Software decoding is not yet supported on Safari. See also https://bitmovin.com/apple-av1-support Signed-off-by: nyanmisaka --- src/scripts/browserDeviceProfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/browserDeviceProfile.js b/src/scripts/browserDeviceProfile.js index 81c6627d57..06afb088a8 100644 --- a/src/scripts/browserDeviceProfile.js +++ b/src/scripts/browserDeviceProfile.js @@ -573,8 +573,8 @@ export default function (options) { const hlsInFmp4VideoCodecs = []; if (canPlayAv1(videoTestElement) - && !browser.mobile && (browser.edgeChromium || browser.firefox || browser.chrome)) { - // disable av1 on mobile since it can be very slow software decoding + && (browser.safari || (!browser.mobile && (browser.edgeChromium || browser.firefox || browser.chrome)))) { + // disable av1 on non-safari mobile browsers since it can be very slow software decoding hlsInFmp4VideoCodecs.push('av1'); } From c469211e532091d55ab79c07acdcf736d918c12c Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 29 Feb 2024 01:27:22 +0800 Subject: [PATCH 27/90] Only enable webm for Safari 15 and 16 webm container is currently broken on Safari 17. Signed-off-by: nyanmisaka --- src/scripts/browserDeviceProfile.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/scripts/browserDeviceProfile.js b/src/scripts/browserDeviceProfile.js index 06afb088a8..51097a475b 100644 --- a/src/scripts/browserDeviceProfile.js +++ b/src/scripts/browserDeviceProfile.js @@ -619,12 +619,20 @@ export default function (options) { if (canPlayVp9) { mp4VideoCodecs.push('vp9'); - webmVideoCodecs.push('vp9'); + // webm support is unreliable on safari 17 + if (!browser.safari + || (browser.safari && browser.versionMajor >= 15 && browser.versionMajor < 17)) { + webmVideoCodecs.push('vp9'); + } } if (canPlayAv1(videoTestElement)) { mp4VideoCodecs.push('av1'); - webmVideoCodecs.push('av1'); + // webm support is unreliable on safari 17 + if (!browser.safari + || (browser.safari && browser.versionMajor >= 15 && browser.versionMajor < 17)) { + webmVideoCodecs.push('av1'); + } } if (canPlayVp8 || browser.tizen) { From 533ae17767cd7c4fba85501f17af1f1910e24bd3 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 28 Feb 2024 21:02:05 +0300 Subject: [PATCH 28/90] Use type import for react FC Co-authored-by: Bill Thornton --- src/components/cardbuilder/Card/Card.tsx | 2 +- src/components/cardbuilder/Card/CardBox.tsx | 3 +-- src/components/cardbuilder/Card/CardContent.tsx | 2 +- src/components/cardbuilder/Card/CardFooterText.tsx | 2 +- src/components/cardbuilder/Card/CardHoverMenu.tsx | 2 +- src/components/cardbuilder/Card/CardImageContainer.tsx | 2 +- src/components/cardbuilder/Card/CardInnerFooter.tsx | 2 +- src/components/cardbuilder/Card/CardOuterFooter.tsx | 2 +- src/components/cardbuilder/Card/CardOverlayButtons.tsx | 2 +- src/components/cardbuilder/Card/CardText.tsx | 2 +- src/components/cardbuilder/Card/CardWrapper.tsx | 2 +- src/components/cardbuilder/Card/Cards.tsx | 2 +- src/components/common/DefaultIconText.tsx | 2 +- src/components/common/DefaultName.tsx | 2 +- src/components/common/Image.tsx | 2 +- src/components/common/InfoIconButton.tsx | 2 +- src/components/common/Media.tsx | 2 +- src/components/common/MoreVertIconButton.tsx | 2 +- src/components/common/NoItemsMessage.tsx | 2 +- src/components/common/PlayArrowIconButton.tsx | 2 +- src/components/common/PlaylistAddIconButton.tsx | 2 +- src/components/common/RightIconButtons.tsx | 2 +- src/components/listview/List/List.tsx | 2 +- src/components/listview/List/ListContent.tsx | 2 +- src/components/listview/List/ListContentWrapper.tsx | 2 +- src/components/listview/List/ListGroupHeaderWrapper.tsx | 2 +- src/components/listview/List/ListImageContainer.tsx | 2 +- src/components/listview/List/ListItemBody.tsx | 2 +- src/components/listview/List/ListTextWrapper.tsx | 2 +- src/components/listview/List/ListViewUserDataButtons.tsx | 2 +- src/components/listview/List/ListWrapper.tsx | 2 +- src/components/listview/List/Lists.tsx | 2 +- src/components/mediainfo/CaptionMediaInfo.tsx | 2 +- src/components/mediainfo/CriticRatingMediaInfo.tsx | 2 +- src/components/mediainfo/EndsAt.tsx | 2 +- src/components/mediainfo/MediaInfoItem.tsx | 2 +- src/components/mediainfo/PrimaryMediaInfo.tsx | 2 +- src/components/mediainfo/StarIcons.tsx | 2 +- src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx | 2 +- src/elements/emby-itemscontainer/ItemsContainer.tsx | 4 ++-- src/elements/emby-playstatebutton/PlayedButton.tsx | 2 +- src/elements/emby-progressbar/AutoTimeProgressBar.tsx | 4 ++-- src/elements/emby-ratingbutton/FavoriteButton.tsx | 2 +- src/elements/emby-scrollbuttons/ScrollButtons.tsx | 2 +- src/elements/emby-scroller/Scroller.tsx | 2 +- 45 files changed, 47 insertions(+), 48 deletions(-) diff --git a/src/components/cardbuilder/Card/Card.tsx b/src/components/cardbuilder/Card/Card.tsx index 2173e0301b..e1718e6459 100644 --- a/src/components/cardbuilder/Card/Card.tsx +++ b/src/components/cardbuilder/Card/Card.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import useCard from './useCard'; import CardWrapper from './CardWrapper'; import CardBox from './CardBox'; diff --git a/src/components/cardbuilder/Card/CardBox.tsx b/src/components/cardbuilder/Card/CardBox.tsx index 430c27b444..34e5044bec 100644 --- a/src/components/cardbuilder/Card/CardBox.tsx +++ b/src/components/cardbuilder/Card/CardBox.tsx @@ -1,5 +1,4 @@ - -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import layoutManager from 'components/layoutManager'; import CardOverlayButtons from './CardOverlayButtons'; diff --git a/src/components/cardbuilder/Card/CardContent.tsx b/src/components/cardbuilder/Card/CardContent.tsx index 11a443fb90..8ebeb0cb87 100644 --- a/src/components/cardbuilder/Card/CardContent.tsx +++ b/src/components/cardbuilder/Card/CardContent.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import classNames from 'classnames'; import { getDefaultBackgroundClass } from '../cardBuilderUtils'; import CardImageContainer from './CardImageContainer'; diff --git a/src/components/cardbuilder/Card/CardFooterText.tsx b/src/components/cardbuilder/Card/CardFooterText.tsx index 87ba3b22ea..b9bf7bbafd 100644 --- a/src/components/cardbuilder/Card/CardFooterText.tsx +++ b/src/components/cardbuilder/Card/CardFooterText.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import Box from '@mui/material/Box'; import useCardText from './useCardText'; import layoutManager from 'components/layoutManager'; diff --git a/src/components/cardbuilder/Card/CardHoverMenu.tsx b/src/components/cardbuilder/Card/CardHoverMenu.tsx index e135d1bd82..b4f56f7188 100644 --- a/src/components/cardbuilder/Card/CardHoverMenu.tsx +++ b/src/components/cardbuilder/Card/CardHoverMenu.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import Box from '@mui/material/Box'; import ButtonGroup from '@mui/material/ButtonGroup'; import classNames from 'classnames'; diff --git a/src/components/cardbuilder/Card/CardImageContainer.tsx b/src/components/cardbuilder/Card/CardImageContainer.tsx index 3b66048e9e..69eb47c66b 100644 --- a/src/components/cardbuilder/Card/CardImageContainer.tsx +++ b/src/components/cardbuilder/Card/CardImageContainer.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import Box from '@mui/material/Box'; import classNames from 'classnames'; import useIndicator from 'components/indicators/useIndicator'; diff --git a/src/components/cardbuilder/Card/CardInnerFooter.tsx b/src/components/cardbuilder/Card/CardInnerFooter.tsx index d6edf853c0..33534e8a9a 100644 --- a/src/components/cardbuilder/Card/CardInnerFooter.tsx +++ b/src/components/cardbuilder/Card/CardInnerFooter.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import classNames from 'classnames'; import CardFooterText from './CardFooterText'; import type { ItemDto } from 'types/itemDto'; diff --git a/src/components/cardbuilder/Card/CardOuterFooter.tsx b/src/components/cardbuilder/Card/CardOuterFooter.tsx index 020a64d584..f03dcb8703 100644 --- a/src/components/cardbuilder/Card/CardOuterFooter.tsx +++ b/src/components/cardbuilder/Card/CardOuterFooter.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import classNames from 'classnames'; import { useApi } from 'hooks/useApi'; import { getCardLogoUrl } from './cardHelper'; diff --git a/src/components/cardbuilder/Card/CardOverlayButtons.tsx b/src/components/cardbuilder/Card/CardOverlayButtons.tsx index c2938c1088..482a14a816 100644 --- a/src/components/cardbuilder/Card/CardOverlayButtons.tsx +++ b/src/components/cardbuilder/Card/CardOverlayButtons.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import ButtonGroup from '@mui/material/ButtonGroup'; import classNames from 'classnames'; import { appRouter } from 'components/router/appRouter'; diff --git a/src/components/cardbuilder/Card/CardText.tsx b/src/components/cardbuilder/Card/CardText.tsx index be6d0b049c..dc64dc61ba 100644 --- a/src/components/cardbuilder/Card/CardText.tsx +++ b/src/components/cardbuilder/Card/CardText.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import Box from '@mui/material/Box'; import escapeHTML from 'escape-html'; import type { TextLine } from './cardHelper'; diff --git a/src/components/cardbuilder/Card/CardWrapper.tsx b/src/components/cardbuilder/Card/CardWrapper.tsx index 01d6446a91..4c8ec854ea 100644 --- a/src/components/cardbuilder/Card/CardWrapper.tsx +++ b/src/components/cardbuilder/Card/CardWrapper.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import layoutManager from 'components/layoutManager'; import type { DataAttributes } from 'types/dataAttributes'; diff --git a/src/components/cardbuilder/Card/Cards.tsx b/src/components/cardbuilder/Card/Cards.tsx index fcf2454a57..82b67cbdfc 100644 --- a/src/components/cardbuilder/Card/Cards.tsx +++ b/src/components/cardbuilder/Card/Cards.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { setCardData } from '../cardBuilder'; import Card from './Card'; import type { ItemDto } from 'types/itemDto'; diff --git a/src/components/common/DefaultIconText.tsx b/src/components/common/DefaultIconText.tsx index 41f0014cb0..60b1aa3fb9 100644 --- a/src/components/common/DefaultIconText.tsx +++ b/src/components/common/DefaultIconText.tsx @@ -1,5 +1,5 @@ import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import Icon from '@mui/material/Icon'; import imageHelper from 'utils/image'; import DefaultName from './DefaultName'; diff --git a/src/components/common/DefaultName.tsx b/src/components/common/DefaultName.tsx index 5946fe27b5..0ead8876a3 100644 --- a/src/components/common/DefaultName.tsx +++ b/src/components/common/DefaultName.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import Box from '@mui/material/Box'; import escapeHTML from 'escape-html'; import itemHelper from 'components/itemHelper'; diff --git a/src/components/common/Image.tsx b/src/components/common/Image.tsx index 14df552660..8e26e78b24 100644 --- a/src/components/common/Image.tsx +++ b/src/components/common/Image.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useState } from 'react'; +import React, { type FC, useCallback, useState } from 'react'; import { BlurhashCanvas } from 'react-blurhash'; import { LazyLoadImage } from 'react-lazy-load-image-component'; diff --git a/src/components/common/InfoIconButton.tsx b/src/components/common/InfoIconButton.tsx index 69c602e327..deefa0628b 100644 --- a/src/components/common/InfoIconButton.tsx +++ b/src/components/common/InfoIconButton.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import IconButton from '@mui/material/IconButton'; import InfoIcon from '@mui/icons-material/Info'; import globalize from 'scripts/globalize'; diff --git a/src/components/common/Media.tsx b/src/components/common/Media.tsx index 170208416f..598c9ec7a5 100644 --- a/src/components/common/Media.tsx +++ b/src/components/common/Media.tsx @@ -1,5 +1,5 @@ import { BaseItemKind, ImageType } from '@jellyfin/sdk/lib/generated-client'; -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import Image from './Image'; import DefaultIconText from './DefaultIconText'; import type { ItemDto } from 'types/itemDto'; diff --git a/src/components/common/MoreVertIconButton.tsx b/src/components/common/MoreVertIconButton.tsx index 231a2afed1..c0a77088c5 100644 --- a/src/components/common/MoreVertIconButton.tsx +++ b/src/components/common/MoreVertIconButton.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import IconButton from '@mui/material/IconButton'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import globalize from 'scripts/globalize'; diff --git a/src/components/common/NoItemsMessage.tsx b/src/components/common/NoItemsMessage.tsx index 2c59b0ed6b..88f288c144 100644 --- a/src/components/common/NoItemsMessage.tsx +++ b/src/components/common/NoItemsMessage.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import globalize from 'scripts/globalize'; diff --git a/src/components/common/PlayArrowIconButton.tsx b/src/components/common/PlayArrowIconButton.tsx index b64fd9bd05..d7ca732966 100644 --- a/src/components/common/PlayArrowIconButton.tsx +++ b/src/components/common/PlayArrowIconButton.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import IconButton from '@mui/material/IconButton'; import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import globalize from 'scripts/globalize'; diff --git a/src/components/common/PlaylistAddIconButton.tsx b/src/components/common/PlaylistAddIconButton.tsx index 19469e0fe3..14fb2a83cd 100644 --- a/src/components/common/PlaylistAddIconButton.tsx +++ b/src/components/common/PlaylistAddIconButton.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import IconButton from '@mui/material/IconButton'; import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd'; import globalize from 'scripts/globalize'; diff --git a/src/components/common/RightIconButtons.tsx b/src/components/common/RightIconButtons.tsx index 2787a1856c..cfe65e451c 100644 --- a/src/components/common/RightIconButtons.tsx +++ b/src/components/common/RightIconButtons.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import IconButton from '@mui/material/IconButton'; interface RightIconButtonsProps { diff --git a/src/components/listview/List/List.tsx b/src/components/listview/List/List.tsx index 995c057526..feafd5a04d 100644 --- a/src/components/listview/List/List.tsx +++ b/src/components/listview/List/List.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import useList from './useList'; import ListContent from './ListContent'; import ListWrapper from './ListWrapper'; diff --git a/src/components/listview/List/ListContent.tsx b/src/components/listview/List/ListContent.tsx index 045c003f73..f9081f0b8a 100644 --- a/src/components/listview/List/ListContent.tsx +++ b/src/components/listview/List/ListContent.tsx @@ -1,5 +1,5 @@ import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import DragHandleIcon from '@mui/icons-material/DragHandle'; import Box from '@mui/material/Box'; diff --git a/src/components/listview/List/ListContentWrapper.tsx b/src/components/listview/List/ListContentWrapper.tsx index 1b0678ad50..59323dec73 100644 --- a/src/components/listview/List/ListContentWrapper.tsx +++ b/src/components/listview/List/ListContentWrapper.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import Box from '@mui/material/Box'; interface ListContentWrapperProps { diff --git a/src/components/listview/List/ListGroupHeaderWrapper.tsx b/src/components/listview/List/ListGroupHeaderWrapper.tsx index f2a131e324..fd17d83120 100644 --- a/src/components/listview/List/ListGroupHeaderWrapper.tsx +++ b/src/components/listview/List/ListGroupHeaderWrapper.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import Typography from '@mui/material/Typography'; interface ListGroupHeaderWrapperProps { diff --git a/src/components/listview/List/ListImageContainer.tsx b/src/components/listview/List/ListImageContainer.tsx index fe77707750..b447b2a701 100644 --- a/src/components/listview/List/ListImageContainer.tsx +++ b/src/components/listview/List/ListImageContainer.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import classNames from 'classnames'; import Box from '@mui/material/Box'; import { useApi } from 'hooks/useApi'; diff --git a/src/components/listview/List/ListItemBody.tsx b/src/components/listview/List/ListItemBody.tsx index 7d033c4f5d..5152040585 100644 --- a/src/components/listview/List/ListItemBody.tsx +++ b/src/components/listview/List/ListItemBody.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import classNames from 'classnames'; import Box from '@mui/material/Box'; import useListTextlines from './useListTextlines'; diff --git a/src/components/listview/List/ListTextWrapper.tsx b/src/components/listview/List/ListTextWrapper.tsx index c2139742ae..675ebe99d4 100644 --- a/src/components/listview/List/ListTextWrapper.tsx +++ b/src/components/listview/List/ListTextWrapper.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; diff --git a/src/components/listview/List/ListViewUserDataButtons.tsx b/src/components/listview/List/ListViewUserDataButtons.tsx index f3ad43ed9e..97668ed999 100644 --- a/src/components/listview/List/ListViewUserDataButtons.tsx +++ b/src/components/listview/List/ListViewUserDataButtons.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { Box } from '@mui/material'; import itemHelper from '../../itemHelper'; import PlayedButton from 'elements/emby-playstatebutton/PlayedButton'; diff --git a/src/components/listview/List/ListWrapper.tsx b/src/components/listview/List/ListWrapper.tsx index a6d4ab292e..76303a0f2b 100644 --- a/src/components/listview/List/ListWrapper.tsx +++ b/src/components/listview/List/ListWrapper.tsx @@ -1,6 +1,6 @@ import classNames from 'classnames'; import escapeHTML from 'escape-html'; -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import layoutManager from '../../layoutManager'; diff --git a/src/components/listview/List/Lists.tsx b/src/components/listview/List/Lists.tsx index ce90622c1f..51f5612bba 100644 --- a/src/components/listview/List/Lists.tsx +++ b/src/components/listview/List/Lists.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import escapeHTML from 'escape-html'; import { groupBy } from 'lodash-es'; import Box from '@mui/material/Box'; diff --git a/src/components/mediainfo/CaptionMediaInfo.tsx b/src/components/mediainfo/CaptionMediaInfo.tsx index 58a6f49af5..497f9fae59 100644 --- a/src/components/mediainfo/CaptionMediaInfo.tsx +++ b/src/components/mediainfo/CaptionMediaInfo.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import classNames from 'classnames'; import ClosedCaptionIcon from '@mui/icons-material/ClosedCaption'; import Box from '@mui/material/Box'; diff --git a/src/components/mediainfo/CriticRatingMediaInfo.tsx b/src/components/mediainfo/CriticRatingMediaInfo.tsx index 080aef78fa..8046c2a931 100644 --- a/src/components/mediainfo/CriticRatingMediaInfo.tsx +++ b/src/components/mediainfo/CriticRatingMediaInfo.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import classNames from 'classnames'; import Box from '@mui/material/Box'; diff --git a/src/components/mediainfo/EndsAt.tsx b/src/components/mediainfo/EndsAt.tsx index 693f949f8b..373e9817d7 100644 --- a/src/components/mediainfo/EndsAt.tsx +++ b/src/components/mediainfo/EndsAt.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import classNames from 'classnames'; import Box from '@mui/material/Box'; import datetime from 'scripts/datetime'; diff --git a/src/components/mediainfo/MediaInfoItem.tsx b/src/components/mediainfo/MediaInfoItem.tsx index b832e02e45..d38635ac2d 100644 --- a/src/components/mediainfo/MediaInfoItem.tsx +++ b/src/components/mediainfo/MediaInfoItem.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import Box from '@mui/material/Box'; import classNames from 'classnames'; import type { MiscInfo } from 'types/mediaInfoItem'; diff --git a/src/components/mediainfo/PrimaryMediaInfo.tsx b/src/components/mediainfo/PrimaryMediaInfo.tsx index 90b640054a..2978a41683 100644 --- a/src/components/mediainfo/PrimaryMediaInfo.tsx +++ b/src/components/mediainfo/PrimaryMediaInfo.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import classNames from 'classnames'; import Box from '@mui/material/Box'; import usePrimaryMediaInfo from './usePrimaryMediaInfo'; diff --git a/src/components/mediainfo/StarIcons.tsx b/src/components/mediainfo/StarIcons.tsx index d253a2db3d..faa09ade3a 100644 --- a/src/components/mediainfo/StarIcons.tsx +++ b/src/components/mediainfo/StarIcons.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import classNames from 'classnames'; import StarIcon from '@mui/icons-material/Star'; import Box from '@mui/material/Box'; diff --git a/src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx b/src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx index aabc709201..67a65703dc 100644 --- a/src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx +++ b/src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useEffect, useState } from 'react'; +import React, { type FC, useCallback, useEffect, useState } from 'react'; import Events, { Event } from 'utils/events'; import serverNotifications from 'scripts/serverNotifications'; import classNames from 'classnames'; diff --git a/src/elements/emby-itemscontainer/ItemsContainer.tsx b/src/elements/emby-itemscontainer/ItemsContainer.tsx index f817221b0c..75f0df99ed 100644 --- a/src/elements/emby-itemscontainer/ItemsContainer.tsx +++ b/src/elements/emby-itemscontainer/ItemsContainer.tsx @@ -1,7 +1,7 @@ import type { LibraryUpdateInfo } from '@jellyfin/sdk/lib/generated-client'; -import React, { FC, useCallback, useEffect, useRef } from 'react'; +import React, { type FC, useCallback, useEffect, useRef } from 'react'; import classNames from 'classnames'; import Box from '@mui/material/Box'; import Sortable from 'sortablejs'; @@ -19,7 +19,7 @@ import itemShortcuts from 'components/shortcuts'; import MultiSelect from 'components/multiSelect/multiSelect'; import loading from 'components/loading/loading'; import focusManager from 'components/focusManager'; -import { ParentId } from 'types/library'; +import type { ParentId } from 'types/library'; function disableEvent(e: MouseEvent) { e.preventDefault(); diff --git a/src/elements/emby-playstatebutton/PlayedButton.tsx b/src/elements/emby-playstatebutton/PlayedButton.tsx index 687953fb91..25434a0912 100644 --- a/src/elements/emby-playstatebutton/PlayedButton.tsx +++ b/src/elements/emby-playstatebutton/PlayedButton.tsx @@ -1,6 +1,6 @@ import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; import { useQueryClient } from '@tanstack/react-query'; -import React, { FC, useCallback } from 'react'; +import React, { type FC, useCallback } from 'react'; import CheckIcon from '@mui/icons-material/Check'; import { IconButton } from '@mui/material'; import classNames from 'classnames'; diff --git a/src/elements/emby-progressbar/AutoTimeProgressBar.tsx b/src/elements/emby-progressbar/AutoTimeProgressBar.tsx index 05b4e6de4a..5dcca778f8 100644 --- a/src/elements/emby-progressbar/AutoTimeProgressBar.tsx +++ b/src/elements/emby-progressbar/AutoTimeProgressBar.tsx @@ -1,7 +1,7 @@ -import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; -import { ProgressOptions } from 'types/progressOptions'; +import React, { type FC, useCallback, useEffect, useRef, useState } from 'react'; import LinearProgress, { linearProgressClasses } from '@mui/material/LinearProgress'; import classNames from 'classnames'; +import type { ProgressOptions } from 'types/progressOptions'; interface AutoTimeProgressBarProps { pct: number; diff --git a/src/elements/emby-ratingbutton/FavoriteButton.tsx b/src/elements/emby-ratingbutton/FavoriteButton.tsx index 673f51c336..2d97fc2747 100644 --- a/src/elements/emby-ratingbutton/FavoriteButton.tsx +++ b/src/elements/emby-ratingbutton/FavoriteButton.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback } from 'react'; +import React, { type FC, useCallback } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import FavoriteIcon from '@mui/icons-material/Favorite'; import { IconButton } from '@mui/material'; diff --git a/src/elements/emby-scrollbuttons/ScrollButtons.tsx b/src/elements/emby-scrollbuttons/ScrollButtons.tsx index 80e8a705c7..050354dfba 100644 --- a/src/elements/emby-scrollbuttons/ScrollButtons.tsx +++ b/src/elements/emby-scrollbuttons/ScrollButtons.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; +import React, { type FC, useCallback, useEffect, useRef, useState } from 'react'; import scrollerFactory from '../../libraries/scroller'; import globalize from '../../scripts/globalize'; import IconButton from '../emby-button/IconButton'; diff --git a/src/elements/emby-scroller/Scroller.tsx b/src/elements/emby-scroller/Scroller.tsx index cb3d5b75b4..1a31101928 100644 --- a/src/elements/emby-scroller/Scroller.tsx +++ b/src/elements/emby-scroller/Scroller.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; +import React, { type FC, useCallback, useEffect, useRef, useState } from 'react'; import classNames from 'classnames'; import useElementSize from 'hooks/useElementSize'; import layoutManager from '../../components/layoutManager'; From 11d013b07ee8523f9829386ef2bd7002eb802989 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 28 Feb 2024 21:18:37 +0300 Subject: [PATCH 29/90] Remove escapeHTML Co-authored-by: Bill Thornton --- src/components/cardbuilder/Card/CardHoverMenu.tsx | 3 +-- src/components/cardbuilder/Card/CardOverlayButtons.tsx | 3 +-- src/components/cardbuilder/Card/CardText.tsx | 3 +-- src/components/cardbuilder/Card/cardHelper.ts | 2 +- src/components/common/DefaultName.tsx | 3 +-- src/components/listview/List/ListWrapper.tsx | 3 +-- src/components/listview/List/Lists.tsx | 3 +-- 7 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/components/cardbuilder/Card/CardHoverMenu.tsx b/src/components/cardbuilder/Card/CardHoverMenu.tsx index b4f56f7188..c09e2bad83 100644 --- a/src/components/cardbuilder/Card/CardHoverMenu.tsx +++ b/src/components/cardbuilder/Card/CardHoverMenu.tsx @@ -2,7 +2,6 @@ import React, { type FC } from 'react'; import Box from '@mui/material/Box'; import ButtonGroup from '@mui/material/ButtonGroup'; import classNames from 'classnames'; -import escapeHTML from 'escape-html'; import { appRouter } from 'components/router/appRouter'; import itemHelper from 'components/itemHelper'; import { playbackManager } from 'components/playback/playbackmanager'; @@ -42,7 +41,7 @@ const CardHoverMenu: FC = ({ > diff --git a/src/components/cardbuilder/Card/CardOverlayButtons.tsx b/src/components/cardbuilder/Card/CardOverlayButtons.tsx index 482a14a816..f3b1b34749 100644 --- a/src/components/cardbuilder/Card/CardOverlayButtons.tsx +++ b/src/components/cardbuilder/Card/CardOverlayButtons.tsx @@ -2,7 +2,6 @@ import React, { type FC } from 'react'; import ButtonGroup from '@mui/material/ButtonGroup'; import classNames from 'classnames'; import { appRouter } from 'components/router/appRouter'; -import escapeHTML from 'escape-html'; import PlayArrowIconButton from '../../common/PlayArrowIconButton'; import MoreVertIconButton from '../../common/MoreVertIconButton'; @@ -61,7 +60,7 @@ const CardOverlayButtons: FC = ({ return ( = ({ className, textLine }) => { title={titleAction.title} {...titleAction.dataAttributes} > - {escapeHTML(titleAction.title)} + {titleAction.title} ); } else { diff --git a/src/components/cardbuilder/Card/cardHelper.ts b/src/components/cardbuilder/Card/cardHelper.ts index b07289dfc2..4ce3247379 100644 --- a/src/components/cardbuilder/Card/cardHelper.ts +++ b/src/components/cardbuilder/Card/cardHelper.ts @@ -216,7 +216,7 @@ function getParentTitle( serverId: NullableString, item: ItemDto ) { - if (isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) { + if (isOuterFooter && item.AlbumArtists?.length) { (item.AlbumArtists[0] as BaseItemDto).Type = BaseItemKind.MusicArtist; (item.AlbumArtists[0] as BaseItemDto).IsFolder = true; return getTextActionButton(item.AlbumArtists[0], null, serverId); diff --git a/src/components/common/DefaultName.tsx b/src/components/common/DefaultName.tsx index 0ead8876a3..ba782e1162 100644 --- a/src/components/common/DefaultName.tsx +++ b/src/components/common/DefaultName.tsx @@ -1,6 +1,5 @@ import React, { type FC } from 'react'; import Box from '@mui/material/Box'; -import escapeHTML from 'escape-html'; import itemHelper from 'components/itemHelper'; import { isUsingLiveTvNaming } from '../cardbuilder/cardBuilderUtils'; import type { ItemDto } from 'types/itemDto'; @@ -15,7 +14,7 @@ const DefaultName: FC = ({ item }) => { itemHelper.getDisplayName(item); return ( - {escapeHTML(defaultName)} + {defaultName} ); }; diff --git a/src/components/listview/List/ListWrapper.tsx b/src/components/listview/List/ListWrapper.tsx index 76303a0f2b..fb03919191 100644 --- a/src/components/listview/List/ListWrapper.tsx +++ b/src/components/listview/List/ListWrapper.tsx @@ -1,5 +1,4 @@ import classNames from 'classnames'; -import escapeHTML from 'escape-html'; import React, { type FC } from 'react'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; @@ -31,7 +30,7 @@ const ListWrapper: FC = ({ 'itemAction listItem-button listItem-focusscale' )} data-action={action} - aria-label={escapeHTML(title)} + aria-label={title} {...dataAttributes} > {children} diff --git a/src/components/listview/List/Lists.tsx b/src/components/listview/List/Lists.tsx index 51f5612bba..1516140635 100644 --- a/src/components/listview/List/Lists.tsx +++ b/src/components/listview/List/Lists.tsx @@ -1,5 +1,4 @@ import React, { type FC } from 'react'; -import escapeHTML from 'escape-html'; import { groupBy } from 'lodash-es'; import Box from '@mui/material/Box'; import { getIndex } from './listHelper'; @@ -43,7 +42,7 @@ const Lists: FC = ({ items = [], listOptions = {} }) => { {itemGroupTitle && ( - {escapeHTML(itemGroupTitle)} + {itemGroupTitle} )} {getItems.map((item) => renderListItem(item, index))} From 8cbddba8fd8cccf2bb5222d65110a94119d17343 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 28 Feb 2024 22:47:36 +0300 Subject: [PATCH 30/90] Use enums CardShape & BaseItemKind, Use type import for react FC, Remove escapeHTML Co-authored-by: Bill Thornton --- .../library/GenresSectionContainer.tsx | 13 ++++---- .../components/library/ItemsView.tsx | 22 ++++++------- .../library/ProgramsSectionView.tsx | 9 ++--- .../library/SuggestionsSectionView.tsx | 14 ++++---- .../components/library/UpcomingView.tsx | 7 ++-- src/components/cardbuilder/Card/CardBox.tsx | 4 +-- .../cardbuilder/Card/CardImageContainer.tsx | 5 +-- .../cardbuilder/Card/CardOverlayButtons.tsx | 8 +++-- src/components/cardbuilder/Card/useCard.ts | 10 +++--- .../cardbuilder/Card/useCardImageUrl.ts | 13 ++++---- .../cardbuilder/cardBuilderUtils.ts | 9 ++--- src/components/indicators/useIndicator.tsx | 2 +- src/components/listview/List/ListWrapper.tsx | 2 +- src/types/cardOptions.ts | 14 +++++--- src/types/listOptions.ts | 7 ++-- src/types/progressOptions.ts | 2 +- src/utils/card.ts | 10 +++++- src/utils/sections.ts | 33 ++++++++++--------- 18 files changed, 102 insertions(+), 82 deletions(-) diff --git a/src/apps/experimental/components/library/GenresSectionContainer.tsx b/src/apps/experimental/components/library/GenresSectionContainer.tsx index 13ba08ced6..39e81052eb 100644 --- a/src/apps/experimental/components/library/GenresSectionContainer.tsx +++ b/src/apps/experimental/components/library/GenresSectionContainer.tsx @@ -1,18 +1,17 @@ import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; +import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields'; import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'; import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; -import escapeHTML from 'escape-html'; -import React, { FC } from 'react'; - +import React, { type FC } from 'react'; import { useGetItems } from 'hooks/useFetchItems'; import Loading from 'components/loading/LoadingComponent'; import { appRouter } from 'components/router/appRouter'; import SectionContainer from './SectionContainer'; -import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; -import { ParentId } from 'types/library'; +import { CardShape } from 'utils/card'; +import type { ParentId } from 'types/library'; interface GenresSectionContainerProps { parentId: ParentId; @@ -60,7 +59,7 @@ const GenresSectionContainer: FC = ({ } return = ({ showTitle: true, centerText: true, cardLayout: false, - shape: collectionType === CollectionType.Music ? 'overflowSquare' : 'overflowPortrait', + shape: collectionType === CollectionType.Music ? CardShape.SquareOverflow : CardShape.PortraitOverflow, showParentTitle: collectionType === CollectionType.Music, showYear: collectionType !== CollectionType.Music }} diff --git a/src/apps/experimental/components/library/ItemsView.tsx b/src/apps/experimental/components/library/ItemsView.tsx index b13ab14165..93be9d1348 100644 --- a/src/apps/experimental/components/library/ItemsView.tsx +++ b/src/apps/experimental/components/library/ItemsView.tsx @@ -1,12 +1,14 @@ import type { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; import { ImageType } from '@jellyfin/sdk/lib/generated-client'; import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; -import React, { FC, useCallback } from 'react'; +import React, { type FC, useCallback } from 'react'; import Box from '@mui/material/Box'; import classNames from 'classnames'; import { useLocalStorage } from 'hooks/useLocalStorage'; import { useGetItem, useGetItemsViewByType } from 'hooks/useFetchItems'; import { getDefaultLibraryViewSettings, getSettingsKey } from 'utils/items'; +import { CardShape } from 'utils/card'; import Loading from 'components/loading/LoadingComponent'; import { playbackManager } from 'components/playback/playbackmanager'; import ItemsContainer from 'elements/emby-itemscontainer/ItemsContainer'; @@ -22,10 +24,8 @@ import GridListViewButton from './GridListViewButton'; import NoItemsMessage from 'components/common/NoItemsMessage'; import Lists from 'components/listview/List/Lists'; import Cards from 'components/cardbuilder/Card/Cards'; -import { type LibraryViewSettings, type ParentId, ViewMode } from 'types/library'; -import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; import { LibraryTab } from 'types/libraryTab'; - +import { type LibraryViewSettings, type ParentId, ViewMode } from 'types/library'; import type { CardOptions } from 'types/cardOptions'; import type { ListOptions } from 'types/listOptions'; @@ -110,18 +110,18 @@ const ItemsView: FC = ({ let preferLogo; if (libraryViewSettings.ImageType === ImageType.Banner) { - shape = 'banner'; + shape = CardShape.Banner; } else if (libraryViewSettings.ImageType === ImageType.Disc) { - shape = 'square'; + shape = CardShape.Square; preferDisc = true; } else if (libraryViewSettings.ImageType === ImageType.Logo) { - shape = 'backdrop'; + shape = CardShape.Backdrop; preferLogo = true; } else if (libraryViewSettings.ImageType === ImageType.Thumb) { - shape = 'backdrop'; + shape = CardShape.Backdrop; preferThumb = true; } else { - shape = 'auto'; + shape = CardShape.Auto; } const cardOptions: CardOptions = { @@ -152,12 +152,12 @@ const ItemsView: FC = ({ cardOptions.showYear = false; cardOptions.overlayPlayButton = true; } else if (viewType === LibraryTab.Channels) { - cardOptions.shape = 'square'; + cardOptions.shape = CardShape.Square; cardOptions.showDetailsMenu = true; cardOptions.showCurrentProgram = true; cardOptions.showCurrentProgramTime = true; } else if (viewType === LibraryTab.SeriesTimers) { - cardOptions.shape = 'backdrop'; + cardOptions.shape = CardShape.Backdrop; cardOptions.showSeriesTimerTime = true; cardOptions.showSeriesTimerChannel = true; cardOptions.overlayMoreButton = true; diff --git a/src/apps/experimental/components/library/ProgramsSectionView.tsx b/src/apps/experimental/components/library/ProgramsSectionView.tsx index b15f319789..960ba1e96f 100644 --- a/src/apps/experimental/components/library/ProgramsSectionView.tsx +++ b/src/apps/experimental/components/library/ProgramsSectionView.tsx @@ -1,11 +1,12 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import { useGetProgramsSectionsWithItems, useGetTimers } from 'hooks/useFetchItems'; import { appRouter } from 'components/router/appRouter'; import globalize from 'scripts/globalize'; import Loading from 'components/loading/LoadingComponent'; import SectionContainer from './SectionContainer'; -import { ParentId } from 'types/library'; -import { Section, SectionType } from 'types/sections'; +import { CardShape } from 'utils/card'; +import type { ParentId } from 'types/library'; +import type { Section, SectionType } from 'types/sections'; interface ProgramsSectionViewProps { parentId: ParentId; @@ -76,7 +77,7 @@ const ProgramsSectionView: FC = ({ items={group.timerInfo ?? []} cardOptions={{ queryKey: ['Timers'], - shape: 'overflowBackdrop', + shape: CardShape.BackdropOverflow, showTitle: true, showParentTitleOrTitle: true, showAirTime: true, diff --git a/src/apps/experimental/components/library/SuggestionsSectionView.tsx b/src/apps/experimental/components/library/SuggestionsSectionView.tsx index d41270e4a6..ca3631e67d 100644 --- a/src/apps/experimental/components/library/SuggestionsSectionView.tsx +++ b/src/apps/experimental/components/library/SuggestionsSectionView.tsx @@ -1,9 +1,8 @@ import { - RecommendationDto, + type RecommendationDto, RecommendationType } from '@jellyfin/sdk/lib/generated-client'; -import React, { FC } from 'react'; -import escapeHTML from 'escape-html'; +import React, { type FC } from 'react'; import { useGetMovieRecommendations, useGetSuggestionSectionsWithItems @@ -12,8 +11,9 @@ import { appRouter } from 'components/router/appRouter'; import globalize from 'scripts/globalize'; import Loading from 'components/loading/LoadingComponent'; import SectionContainer from './SectionContainer'; -import { ParentId } from 'types/library'; -import { Section, SectionType } from 'types/sections'; +import { CardShape } from 'utils/card'; +import type { ParentId } from 'types/library'; +import type { Section, SectionType } from 'types/sections'; interface SuggestionsSectionViewProps { parentId: ParentId; @@ -89,7 +89,7 @@ const SuggestionsSectionView: FC = ({ ); break; } - return escapeHTML(title); + return title; }; return ( @@ -119,7 +119,7 @@ const SuggestionsSectionView: FC = ({ items={recommendation.Items ?? []} cardOptions={{ queryKey: ['MovieRecommendations'], - shape: 'overflowPortrait', + shape: CardShape.PortraitOverflow, showYear: true, scalable: true, overlayPlayButton: true, diff --git a/src/apps/experimental/components/library/UpcomingView.tsx b/src/apps/experimental/components/library/UpcomingView.tsx index bf6a6b0ace..874382d9e5 100644 --- a/src/apps/experimental/components/library/UpcomingView.tsx +++ b/src/apps/experimental/components/library/UpcomingView.tsx @@ -1,10 +1,11 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import Box from '@mui/material/Box'; import { useGetGroupsUpcomingEpisodes } from 'hooks/useFetchItems'; import Loading from 'components/loading/LoadingComponent'; import globalize from 'scripts/globalize'; import SectionContainer from './SectionContainer'; -import { LibraryViewProps } from 'types/library'; +import { CardShape } from 'utils/card'; +import type { LibraryViewProps } from 'types/library'; const UpcomingView: FC = ({ parentId }) => { const { isLoading, data: groupsUpcomingEpisodes } = useGetGroupsUpcomingEpisodes(parentId); @@ -29,7 +30,7 @@ const UpcomingView: FC = ({ parentId }) => { sectionTitle={group.name} items={group.items ?? []} cardOptions={{ - shape: 'overflowBackdrop', + shape: CardShape.BackdropOverflow, showLocationTypeIndicator: false, showParentTitle: true, preferThumb: true, diff --git a/src/components/cardbuilder/Card/CardBox.tsx b/src/components/cardbuilder/Card/CardBox.tsx index 34e5044bec..07c8bc2d67 100644 --- a/src/components/cardbuilder/Card/CardBox.tsx +++ b/src/components/cardbuilder/Card/CardBox.tsx @@ -5,7 +5,7 @@ import CardOverlayButtons from './CardOverlayButtons'; import CardHoverMenu from './CardHoverMenu'; import CardOuterFooter from './CardOuterFooter'; import CardContent from './CardContent'; - +import { CardShape } from 'utils/card'; import type { ItemDto } from 'types/itemDto'; import type { CardOptions } from 'types/cardOptions'; @@ -13,7 +13,7 @@ interface CardBoxProps { item: ItemDto; cardOptions: CardOptions; className: string; - shape: string | null | undefined; + shape: CardShape | undefined; imgUrl: string | undefined; blurhash: string | undefined; forceName: boolean; diff --git a/src/components/cardbuilder/Card/CardImageContainer.tsx b/src/components/cardbuilder/Card/CardImageContainer.tsx index 69eb47c66b..8a10b6b330 100644 --- a/src/components/cardbuilder/Card/CardImageContainer.tsx +++ b/src/components/cardbuilder/Card/CardImageContainer.tsx @@ -1,3 +1,4 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; import React, { type FC } from 'react'; import Box from '@mui/material/Box'; import classNames from 'classnames'; @@ -32,7 +33,7 @@ const CardImageContainer: FC = ({ const cardImageClass = classNames( 'cardImageContainer', { coveredImage: coveredImage }, - { 'coveredImage-contain': coveredImage && item.Type === 'TvChannel' } + { 'coveredImage-contain': coveredImage && item.Type === BaseItemKind.TvChannel } ); return ( @@ -52,7 +53,7 @@ const CardImageContainer: FC = ({ indicator.getChildCountIndicator() : indicator.getPlayedIndicator()} - {(item.Type === 'CollectionFolder' + {(item.Type === BaseItemKind.CollectionFolder || item.CollectionType) && item.RefreshProgress && ( diff --git a/src/components/cardbuilder/Card/CardOverlayButtons.tsx b/src/components/cardbuilder/Card/CardOverlayButtons.tsx index f3b1b34749..66abd459e3 100644 --- a/src/components/cardbuilder/Card/CardOverlayButtons.tsx +++ b/src/components/cardbuilder/Card/CardOverlayButtons.tsx @@ -1,3 +1,5 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { LocationType } from '@jellyfin/sdk/lib/generated-client/models/location-type'; import React, { type FC } from 'react'; import ButtonGroup from '@mui/material/ButtonGroup'; import classNames from 'classnames'; @@ -15,10 +17,10 @@ const sholudShowOverlayPlayButton = ( return ( overlayPlayButton && !item.IsPlaceHolder - && (item.LocationType !== 'Virtual' + && (item.LocationType !== LocationType.Virtual || !item.MediaType - || item.Type === 'Program') - && item.Type !== 'Person' + || item.Type === BaseItemKind.Program) + && item.Type !== BaseItemKind.Person ); }; diff --git a/src/components/cardbuilder/Card/useCard.ts b/src/components/cardbuilder/Card/useCard.ts index 5751471801..80c5dd69b7 100644 --- a/src/components/cardbuilder/Card/useCard.ts +++ b/src/components/cardbuilder/Card/useCard.ts @@ -1,3 +1,4 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; import classNames from 'classnames'; import useCardImageUrl from './useCardImageUrl'; import { @@ -5,6 +6,7 @@ import { resolveMixedShapeByAspectRatio } from '../cardBuilderUtils'; import { getDataAttributes } from 'utils/items'; +import { CardShape } from 'utils/card'; import layoutManager from 'components/layoutManager'; import type { ItemDto } from 'types/itemDto'; @@ -24,7 +26,7 @@ function useCard({ item, cardOptions }: UseCardProps) { let shape = cardOptions.shape; - if (shape === 'mixed') { + if (shape === CardShape.Mixed) { shape = resolveMixedShapeByAspectRatio(item.PrimaryImageAspectRatio); } @@ -82,9 +84,9 @@ function useCard({ item, cardOptions }: UseCardProps) { { groupedCard: cardOptions.showChildCountIndicator && item.ChildCount }, { 'card-withuserdata': - item.Type !== 'MusicAlbum' - && item.Type !== 'MusicArtist' - && item.Type !== 'Audio' + item.Type !== BaseItemKind.MusicAlbum + && item.Type !== BaseItemKind.MusicArtist + && item.Type !== BaseItemKind.Audio }, { itemAction: layoutManager.tv } ); diff --git a/src/components/cardbuilder/Card/useCardImageUrl.ts b/src/components/cardbuilder/Card/useCardImageUrl.ts index 3e1997d542..1d1fca2dc7 100644 --- a/src/components/cardbuilder/Card/useCardImageUrl.ts +++ b/src/components/cardbuilder/Card/useCardImageUrl.ts @@ -1,8 +1,9 @@ -import { BaseItemKind, ImageType } from '@jellyfin/sdk/lib/generated-client'; +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'; import { getImageApi } from '@jellyfin/sdk/lib/utils/api/image-api'; import { useApi } from 'hooks/useApi'; import { getDesiredAspect } from '../cardBuilderUtils'; - +import { CardShape } from 'utils/card'; import type { ItemDto, NullableNumber, NullableString } from 'types/itemDto'; import type { CardOptions } from 'types/cardOptions'; @@ -98,10 +99,10 @@ function isCoverImage( function shouldShowPreferBanner( imageTagsBanner: NullableString, cardOptions: CardOptions, - shape: NullableString + shape: CardShape | undefined ): boolean { return ( - (cardOptions.preferBanner || shape === 'banner') + (cardOptions.preferBanner || shape === CardShape.Banner) && Boolean(imageTagsBanner) ); } @@ -152,7 +153,7 @@ function shouldShowPreferThumb(itemType: NullableString, cardOptions: CardOption function getCardImageInfo( item: ItemDto, cardOptions: CardOptions, - shape: NullableString + shape: CardShape | undefined ) { const width = cardOptions.width; let height; @@ -251,7 +252,7 @@ function getCardImageInfo( interface UseCardImageUrlProps { item: ItemDto; cardOptions: CardOptions; - shape: NullableString; + shape: CardShape | undefined; } function useCardImageUrl({ item, cardOptions, shape }: UseCardImageUrlProps) { diff --git a/src/components/cardbuilder/cardBuilderUtils.ts b/src/components/cardbuilder/cardBuilderUtils.ts index 3ac471ccb1..ecd1d375b5 100644 --- a/src/components/cardbuilder/cardBuilderUtils.ts +++ b/src/components/cardbuilder/cardBuilderUtils.ts @@ -1,3 +1,4 @@ +import { CardShape } from 'utils/card'; import { randomInt } from '../../utils/number'; import classNames from 'classnames'; @@ -54,15 +55,15 @@ export const isResizable = (windowWidth: number): boolean => { */ export const resolveMixedShapeByAspectRatio = (primaryImageAspectRatio: number | null | undefined) => { if (primaryImageAspectRatio === undefined || primaryImageAspectRatio === null) { - return 'mixedSquare'; + return CardShape.MixedSquare; } if (primaryImageAspectRatio >= 1.33) { - return 'mixedBackdrop'; + return CardShape.MixedBackdrop; } else if (primaryImageAspectRatio > 0.71) { - return 'mixedSquare'; + return CardShape.MixedSquare; } else { - return 'mixedPortrait'; + return CardShape.MixedPortrait; } }; diff --git a/src/components/indicators/useIndicator.tsx b/src/components/indicators/useIndicator.tsx index 214c736267..d6e3df180e 100644 --- a/src/components/indicators/useIndicator.tsx +++ b/src/components/indicators/useIndicator.tsx @@ -193,7 +193,7 @@ const useIndicator = (item: ItemDto) => { const getProgressBar = (progressOptions?: ProgressOptions) => { if ( enableProgressIndicator(item.Type, item.MediaType) - && item.Type !== 'Recording' + && item.Type !== BaseItemKind.Recording ) { const playedPercentage = progressOptions?.userData?.PlayedPercentage ? progressOptions.userData.PlayedPercentage : diff --git a/src/components/listview/List/ListWrapper.tsx b/src/components/listview/List/ListWrapper.tsx index fb03919191..9b394f9839 100644 --- a/src/components/listview/List/ListWrapper.tsx +++ b/src/components/listview/List/ListWrapper.tsx @@ -30,7 +30,7 @@ const ListWrapper: FC = ({ 'itemAction listItem-button listItem-focusscale' )} data-action={action} - aria-label={title} + aria-label={title || ''} {...dataAttributes} > {children} diff --git a/src/types/cardOptions.ts b/src/types/cardOptions.ts index 3745d804a4..19cea6a272 100644 --- a/src/types/cardOptions.ts +++ b/src/types/cardOptions.ts @@ -1,7 +1,11 @@ -import type { BaseItemDtoImageBlurHashes, BaseItemKind, ImageType, UserItemDataDto } from '@jellyfin/sdk/lib/generated-client'; -import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; +import type { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import type { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'; +import type { UserItemDataDto } from '@jellyfin/sdk/lib/generated-client/models/user-item-data-dto'; +import type { BaseItemDtoImageBlurHashes } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto-image-blur-hashes'; +import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; +import { CardShape } from 'utils/card'; import type { ItemDto, NullableString } from './itemDto'; -import { ParentId } from './library'; +import type { ParentId } from './library'; export interface CardOptions { itemsContainer?: HTMLElement | null; @@ -20,7 +24,8 @@ export interface CardOptions { preferDisc?: boolean; preferLogo?: boolean; scalable?: boolean; - shape?: string | null; + shape?: CardShape; + defaultShape?: CardShape; lazy?: boolean; cardLayout?: boolean | null; showParentTitle?: boolean; @@ -39,7 +44,6 @@ export interface CardOptions { lines?: number; context?: CollectionType; action?: string | null; - defaultShape?: string; indexBy?: string; parentId?: ParentId; showMenu?: boolean; diff --git a/src/types/listOptions.ts b/src/types/listOptions.ts index 7df383a48b..b9b5eea80a 100644 --- a/src/types/listOptions.ts +++ b/src/types/listOptions.ts @@ -1,9 +1,8 @@ -import { BaseItemDto, SeriesTimerInfoDto } from '@jellyfin/sdk/lib/generated-client'; import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; -import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; - +import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; +import type { ItemDto } from './itemDto'; export interface ListOptions { - items?: BaseItemDto[] | SeriesTimerInfoDto[] | null; + items?: ItemDto[] | null; index?: string; showIndex?: boolean; action?: string | null; diff --git a/src/types/progressOptions.ts b/src/types/progressOptions.ts index ae043f2066..fd5ed7e468 100644 --- a/src/types/progressOptions.ts +++ b/src/types/progressOptions.ts @@ -1,4 +1,4 @@ -import { UserItemDataDto } from '@jellyfin/sdk/lib/generated-client'; +import type { UserItemDataDto } from '@jellyfin/sdk/lib/generated-client/models/user-item-data-dto'; export interface ProgressOptions { containerClass: string, diff --git a/src/utils/card.ts b/src/utils/card.ts index bdb68e7243..950964054a 100644 --- a/src/utils/card.ts +++ b/src/utils/card.ts @@ -5,7 +5,15 @@ export enum CardShape { Portrait = 'portrait', PortraitOverflow = 'overflowPortrait', Square = 'square', - SquareOverflow = 'overflowSquare' + SquareOverflow = 'overflowSquare', + Auto = 'auto', + AutoHome = 'autohome', + AutoOverflow = 'autooverflow', + AutoVertical = 'autoVertical', + Mixed = 'mixed', + MixedSquare = 'mixedSquare', + MixedBackdrop = 'mixedBackdrop', + MixedPortrait = 'mixedPortrait', } export function getSquareShape(enableOverflow = true) { diff --git a/src/utils/sections.ts b/src/utils/sections.ts index 4617ea7f14..ce78417da8 100644 --- a/src/utils/sections.ts +++ b/src/utils/sections.ts @@ -3,6 +3,7 @@ import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-ite import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; import * as userSettings from 'scripts/settings/userSettings'; +import { CardShape } from 'utils/card'; import { Section, SectionType, SectionApiMethod } from 'types/sections'; export const getSuggestionSections = (): Section[] => { @@ -29,7 +30,7 @@ export const getSuggestionSections = (): Section[] => { cardOptions: { overlayPlayButton: true, preferThumb: true, - shape: 'overflowBackdrop', + shape: CardShape.BackdropOverflow, showYear: true } }, @@ -43,7 +44,7 @@ export const getSuggestionSections = (): Section[] => { }, cardOptions: { overlayPlayButton: true, - shape: 'overflowPortrait', + shape: CardShape.PortraitOverflow, showYear: true } }, @@ -57,7 +58,7 @@ export const getSuggestionSections = (): Section[] => { }, cardOptions: { overlayPlayButton: true, - shape: 'overflowBackdrop', + shape: CardShape.BackdropOverflow, preferThumb: true, inheritThumb: !userSettings.useEpisodeImagesInNextUpAndResume(undefined), @@ -74,7 +75,7 @@ export const getSuggestionSections = (): Section[] => { }, cardOptions: { overlayPlayButton: true, - shape: 'overflowBackdrop', + shape: CardShape.BackdropOverflow, preferThumb: true, showSeriesYear: true, showParentTitle: true, @@ -90,7 +91,7 @@ export const getSuggestionSections = (): Section[] => { type: SectionType.NextUp, cardOptions: { overlayPlayButton: true, - shape: 'overflowBackdrop', + shape: CardShape.BackdropOverflow, preferThumb: true, inheritThumb: !userSettings.useEpisodeImagesInNextUpAndResume(undefined), @@ -107,7 +108,7 @@ export const getSuggestionSections = (): Section[] => { }, cardOptions: { showUnplayedIndicator: false, - shape: 'overflowSquare', + shape: CardShape.SquareOverflow, showParentTitle: true, overlayPlayButton: true, coverImage: true @@ -125,7 +126,7 @@ export const getSuggestionSections = (): Section[] => { }, cardOptions: { showUnplayedIndicator: false, - shape: 'overflowSquare', + shape: CardShape.SquareOverflow, showParentTitle: true, action: 'instantmix', overlayMoreButton: true, @@ -144,7 +145,7 @@ export const getSuggestionSections = (): Section[] => { }, cardOptions: { showUnplayedIndicator: false, - shape: 'overflowSquare', + shape: CardShape.SquareOverflow, showParentTitle: true, action: 'instantmix', overlayMoreButton: true, @@ -157,8 +158,8 @@ export const getSuggestionSections = (): Section[] => { export const getProgramSections = (): Section[] => { const cardOptions = { inheritThumb: false, - shape: 'autooverflow', - defaultShape: 'overflowBackdrop', + shape: CardShape.AutoOverflow, + defaultShape: CardShape.BackdropOverflow, centerText: true, coverImage: true, overlayText: false, @@ -309,8 +310,8 @@ export const getProgramSections = (): Section[] => { cardOptions: { showYear: true, lines: 2, - shape: 'autooverflow', - defaultShape: 'overflowBackdrop', + shape: CardShape.AutoOverflow, + defaultShape: CardShape.BackdropOverflow, showTitle: true, showParentTitle: true, coverImage: true, @@ -328,8 +329,8 @@ export const getProgramSections = (): Section[] => { cardOptions: { showYear: false, showParentTitle: false, - shape: 'autooverflow', - defaultShape: 'overflowBackdrop', + shape: CardShape.AutoOverflow, + defaultShape: CardShape.BackdropOverflow, showTitle: true, coverImage: true, cardLayout: false, @@ -347,8 +348,8 @@ export const getProgramSections = (): Section[] => { isInProgress: true }, cardOptions: { - shape: 'autooverflow', - defaultShape: 'backdrop', + shape: CardShape.AutoOverflow, + defaultShape: CardShape.Backdrop, showParentTitle: false, showParentTitleOrTitle: true, showTitle: true, From 511f8340ef4315abfeb61a0ce84e799f5b1461b0 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Wed, 28 Feb 2024 23:10:31 +0300 Subject: [PATCH 31/90] Replace jellyfin sdk generated-client models wiyh full path Co-authored-by: Bill Thornton --- .../components/library/GenresItemsContainer.tsx | 6 +++--- src/apps/experimental/components/library/GenresView.tsx | 6 +++--- .../experimental/components/library/PageTabContent.tsx | 8 ++++---- src/components/common/DefaultIconText.tsx | 2 +- src/components/indicators/useIndicator.tsx | 2 +- src/components/listview/List/ListContent.tsx | 2 +- src/components/listview/List/useListTextlines.tsx | 2 +- src/components/mediainfo/usePrimaryMediaInfo.tsx | 2 +- src/elements/emby-itemscontainer/ItemsContainer.tsx | 2 +- src/elements/emby-playstatebutton/PlayedButton.tsx | 4 ++-- src/elements/emby-ratingbutton/FavoriteButton.tsx | 2 +- src/types/libraryTabContent.ts | 8 ++++---- src/utils/image.ts | 2 +- src/utils/sections.ts | 6 ++++-- 14 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/apps/experimental/components/library/GenresItemsContainer.tsx b/src/apps/experimental/components/library/GenresItemsContainer.tsx index b3f3b5f7bf..a676a0c78b 100644 --- a/src/apps/experimental/components/library/GenresItemsContainer.tsx +++ b/src/apps/experimental/components/library/GenresItemsContainer.tsx @@ -1,11 +1,11 @@ -import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import type { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; import React, { FC } from 'react'; import { useGetGenres } from 'hooks/useFetchItems'; import globalize from 'scripts/globalize'; import Loading from 'components/loading/LoadingComponent'; import GenresSectionContainer from './GenresSectionContainer'; -import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; -import { ParentId } from 'types/library'; +import type { ParentId } from 'types/library'; interface GenresItemsContainerProps { parentId: ParentId; diff --git a/src/apps/experimental/components/library/GenresView.tsx b/src/apps/experimental/components/library/GenresView.tsx index 9076c28c6d..50d8c68507 100644 --- a/src/apps/experimental/components/library/GenresView.tsx +++ b/src/apps/experimental/components/library/GenresView.tsx @@ -1,8 +1,8 @@ -import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import type { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; import React, { FC } from 'react'; import GenresItemsContainer from './GenresItemsContainer'; -import { ParentId } from 'types/library'; -import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; +import type { ParentId } from 'types/library'; interface GenresViewProps { parentId: ParentId; diff --git a/src/apps/experimental/components/library/PageTabContent.tsx b/src/apps/experimental/components/library/PageTabContent.tsx index 9726f2ffa5..6cd99c5575 100644 --- a/src/apps/experimental/components/library/PageTabContent.tsx +++ b/src/apps/experimental/components/library/PageTabContent.tsx @@ -1,13 +1,13 @@ -import React, { FC } from 'react'; +import React, { type FC } from 'react'; import SuggestionsSectionView from './SuggestionsSectionView'; import UpcomingView from './UpcomingView'; import GenresView from './GenresView'; import ItemsView from './ItemsView'; -import { LibraryTab } from 'types/libraryTab'; -import { ParentId } from 'types/library'; -import { LibraryTabContent } from 'types/libraryTabContent'; import GuideView from './GuideView'; import ProgramsSectionView from './ProgramsSectionView'; +import { LibraryTab } from 'types/libraryTab'; +import type { ParentId } from 'types/library'; +import type { LibraryTabContent } from 'types/libraryTabContent'; interface PageTabContentProps { parentId: ParentId; diff --git a/src/components/common/DefaultIconText.tsx b/src/components/common/DefaultIconText.tsx index 60b1aa3fb9..f6af526c07 100644 --- a/src/components/common/DefaultIconText.tsx +++ b/src/components/common/DefaultIconText.tsx @@ -1,4 +1,4 @@ -import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; import React, { type FC } from 'react'; import Icon from '@mui/material/Icon'; import imageHelper from 'utils/image'; diff --git a/src/components/indicators/useIndicator.tsx b/src/components/indicators/useIndicator.tsx index d6e3df180e..f54d76d9cd 100644 --- a/src/components/indicators/useIndicator.tsx +++ b/src/components/indicators/useIndicator.tsx @@ -1,5 +1,5 @@ import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; -import { LocationType } from '@jellyfin/sdk/lib/generated-client'; +import { LocationType } from '@jellyfin/sdk/lib/generated-client/models/location-type'; import React from 'react'; import Box from '@mui/material/Box'; import LinearProgress, { diff --git a/src/components/listview/List/ListContent.tsx b/src/components/listview/List/ListContent.tsx index f9081f0b8a..0cf2160ca6 100644 --- a/src/components/listview/List/ListContent.tsx +++ b/src/components/listview/List/ListContent.tsx @@ -1,4 +1,4 @@ -import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; import React, { type FC } from 'react'; import DragHandleIcon from '@mui/icons-material/DragHandle'; import Box from '@mui/material/Box'; diff --git a/src/components/listview/List/useListTextlines.tsx b/src/components/listview/List/useListTextlines.tsx index cb5f7ceeb8..da66673a5a 100644 --- a/src/components/listview/List/useListTextlines.tsx +++ b/src/components/listview/List/useListTextlines.tsx @@ -1,5 +1,5 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; import React from 'react'; -import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; import itemHelper from '../../itemHelper'; import datetime from 'scripts/datetime'; import ListTextWrapper from './ListTextWrapper'; diff --git a/src/components/mediainfo/usePrimaryMediaInfo.tsx b/src/components/mediainfo/usePrimaryMediaInfo.tsx index 480f31dbbc..b702bc42ce 100644 --- a/src/components/mediainfo/usePrimaryMediaInfo.tsx +++ b/src/components/mediainfo/usePrimaryMediaInfo.tsx @@ -1,4 +1,4 @@ -import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; import * as userSettings from 'scripts/settings/userSettings'; import datetime from 'scripts/datetime'; import globalize from 'scripts/globalize'; diff --git a/src/elements/emby-itemscontainer/ItemsContainer.tsx b/src/elements/emby-itemscontainer/ItemsContainer.tsx index 75f0df99ed..779e718271 100644 --- a/src/elements/emby-itemscontainer/ItemsContainer.tsx +++ b/src/elements/emby-itemscontainer/ItemsContainer.tsx @@ -174,7 +174,7 @@ const ItemsContainer: FC = ({ const invalidateQueries = useCallback(async () => { await queryClient.invalidateQueries({ - queryKey: queryKey, + queryKey, type: 'all', refetchType: 'active' }); diff --git a/src/elements/emby-playstatebutton/PlayedButton.tsx b/src/elements/emby-playstatebutton/PlayedButton.tsx index 25434a0912..6a15940686 100644 --- a/src/elements/emby-playstatebutton/PlayedButton.tsx +++ b/src/elements/emby-playstatebutton/PlayedButton.tsx @@ -1,4 +1,4 @@ -import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; import { useQueryClient } from '@tanstack/react-query'; import React, { type FC, useCallback } from 'react'; import CheckIcon from '@mui/icons-material/Check'; @@ -48,7 +48,7 @@ const PlayedButton: FC = ({ }, { onSuccess: async() => { await queryClient.invalidateQueries({ - queryKey: queryKey, + queryKey, type: 'all', refetchType: 'active' }); diff --git a/src/elements/emby-ratingbutton/FavoriteButton.tsx b/src/elements/emby-ratingbutton/FavoriteButton.tsx index 2d97fc2747..2bbdeef4ab 100644 --- a/src/elements/emby-ratingbutton/FavoriteButton.tsx +++ b/src/elements/emby-ratingbutton/FavoriteButton.tsx @@ -34,7 +34,7 @@ const FavoriteButton: FC = ({ }, { onSuccess: async() => { await queryClient.invalidateQueries({ - queryKey: queryKey, + queryKey, type: 'all', refetchType: 'active' }); diff --git a/src/types/libraryTabContent.ts b/src/types/libraryTabContent.ts index 433744cd22..547d025134 100644 --- a/src/types/libraryTabContent.ts +++ b/src/types/libraryTabContent.ts @@ -1,7 +1,7 @@ -import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; -import { LibraryTab } from './libraryTab'; -import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; -import { SectionType } from './sections'; +import type { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; +import type { LibraryTab } from './libraryTab'; +import type { SectionType } from './sections'; export interface SectionsView { suggestionSections?: SectionType[]; diff --git a/src/utils/image.ts b/src/utils/image.ts index 3819f865df..dc32e87973 100644 --- a/src/utils/image.ts +++ b/src/utils/image.ts @@ -1,4 +1,4 @@ -import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'; +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; import type { DeviceInfo } from '@jellyfin/sdk/lib/generated-client/models/device-info'; import type { SessionInfo } from '@jellyfin/sdk/lib/generated-client/models/session-info'; diff --git a/src/utils/sections.ts b/src/utils/sections.ts index ce78417da8..70c74b598e 100644 --- a/src/utils/sections.ts +++ b/src/utils/sections.ts @@ -1,10 +1,12 @@ -import { ImageType, ItemFields, ItemFilter } from '@jellyfin/sdk/lib/generated-client'; import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields'; +import { ItemFilter } from '@jellyfin/sdk/lib/generated-client/models/item-filter'; +import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'; import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; import * as userSettings from 'scripts/settings/userSettings'; import { CardShape } from 'utils/card'; -import { Section, SectionType, SectionApiMethod } from 'types/sections'; +import { type Section, SectionType, SectionApiMethod } from 'types/sections'; export const getSuggestionSections = (): Section[] => { const parametersOptions = { From 7b43e83a65e4a1d17dc2436b97fcae1d2948471f Mon Sep 17 00:00:00 2001 From: Ronan Fitzgerald Date: Wed, 28 Feb 2024 21:03:37 +0000 Subject: [PATCH 32/90] Translated using Weblate (English (United Kingdom)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/en_GB/ --- src/strings/en-gb.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/strings/en-gb.json b/src/strings/en-gb.json index 6849dc19f4..e4e9e77c1f 100644 --- a/src/strings/en-gb.json +++ b/src/strings/en-gb.json @@ -1782,5 +1782,9 @@ "SearchResultsEmpty": "Sorry! No results found for \"{0}\"", "LabelSelectAudioNormalization": "Audio Normalisation", "LabelTrackGain": "Track Gain", - "HeaderAllRecordings": "All Recordings" + "HeaderAllRecordings": "All Recordings", + "ButtonEditUser": "Edit user", + "LabelBuildVersion": "Build version", + "LabelServerVersion": "Server version", + "LabelWebVersion": "Web version" } From a314995911cc8c72fb6852339685febc769d22e2 Mon Sep 17 00:00:00 2001 From: daydreamer-json Date: Wed, 28 Feb 2024 20:56:05 +0000 Subject: [PATCH 33/90] Translated using Weblate (Japanese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ja/ --- src/strings/ja.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/ja.json b/src/strings/ja.json index f86941322d..0d848d370f 100644 --- a/src/strings/ja.json +++ b/src/strings/ja.json @@ -1765,5 +1765,6 @@ "LogoScreensaver": "ロゴスクリーンセーバー", "AiTranslated": "AI翻訳", "MachineTranslated": "機械翻訳", - "BackdropScreensaver": "背景スクリーンセーバー" + "BackdropScreensaver": "背景スクリーンセーバー", + "ButtonEditUser": "ユーザーを編集" } From bbc1860bdec1c5b71d6e778d7fe1fc2c84a65332 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Thu, 29 Feb 2024 04:21:24 +0300 Subject: [PATCH 34/90] Replace hardcoded color with theme reference Co-authored-by: Bill Thornton --- src/components/mediainfo/StarIcons.tsx | 4 +++- .../emby-progressbar/AutoTimeProgressBar.tsx | 6 ++++-- src/themes/theme.ts | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/components/mediainfo/StarIcons.tsx b/src/components/mediainfo/StarIcons.tsx index faa09ade3a..0d38453d7e 100644 --- a/src/components/mediainfo/StarIcons.tsx +++ b/src/components/mediainfo/StarIcons.tsx @@ -2,6 +2,7 @@ import React, { type FC } from 'react'; import classNames from 'classnames'; import StarIcon from '@mui/icons-material/Star'; import Box from '@mui/material/Box'; +import { useTheme } from '@mui/material/styles'; interface StarIconsProps { className?: string; @@ -9,6 +10,7 @@ interface StarIconsProps { } const StarIcons: FC = ({ className, communityRating }) => { + const theme = useTheme(); const cssClass = classNames( 'mediaInfoItem', 'mediaInfoText', @@ -19,7 +21,7 @@ const StarIcons: FC = ({ className, communityRating }) => { return ( {communityRating.toFixed(1)} diff --git a/src/elements/emby-progressbar/AutoTimeProgressBar.tsx b/src/elements/emby-progressbar/AutoTimeProgressBar.tsx index 5dcca778f8..b6e5910cb6 100644 --- a/src/elements/emby-progressbar/AutoTimeProgressBar.tsx +++ b/src/elements/emby-progressbar/AutoTimeProgressBar.tsx @@ -1,6 +1,7 @@ import React, { type FC, useCallback, useEffect, useRef, useState } from 'react'; -import LinearProgress, { linearProgressClasses } from '@mui/material/LinearProgress'; import classNames from 'classnames'; +import LinearProgress, { linearProgressClasses } from '@mui/material/LinearProgress'; +import { useTheme } from '@mui/material/styles'; import type { ProgressOptions } from 'types/progressOptions'; interface AutoTimeProgressBarProps { @@ -22,6 +23,7 @@ const AutoTimeProgressBar: FC = ({ }) => { const [progress, setProgress] = useState(pct); const timerRef = useRef | null>(null); + const theme = useTheme(); const onAutoTimeProgress = useCallback(() => { const start = parseInt(starTtime.toString(), 10); @@ -67,7 +69,7 @@ const AutoTimeProgressBar: FC = ({ sx={{ [`& .${linearProgressClasses.bar}`]: { borderRadius: 5, - backgroundColor: isRecording ? '#cb272a' : '#00a4dc' + backgroundColor: isRecording ? theme.palette.error.main : theme.palette.primary.main } }} /> diff --git a/src/themes/theme.ts b/src/themes/theme.ts index a5230ebafd..70e060dd74 100644 --- a/src/themes/theme.ts +++ b/src/themes/theme.ts @@ -1,5 +1,15 @@ import { createTheme } from '@mui/material/styles'; +declare module '@mui/material/styles' { + interface Palette { + starIcon: Palette['primary']; + } + + interface PaletteOptions { + starIcon?: PaletteOptions['primary']; + } +} + const LIST_ICON_WIDTH = 36; /** The default Jellyfin app theme for mui */ @@ -18,6 +28,12 @@ const theme = createTheme({ }, action: { selectedOpacity: 0.2 + }, + starIcon: { + main: '#f2b01e' // Yellow color + }, + error: { + main: '#cb272a' // Red color } }, typography: { From 90a1d065579a5aea5dd17f5d301ebdcafacd5475 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Thu, 29 Feb 2024 04:22:13 +0300 Subject: [PATCH 35/90] separate shared types Co-authored-by: Bill Thornton --- src/components/cardbuilder/Card/cardHelper.ts | 3 ++- src/components/cardbuilder/Card/useCardImageUrl.ts | 3 ++- src/components/cardbuilder/cardBuilderUtils.ts | 2 +- src/components/indicators/useIndicator.tsx | 3 ++- src/components/mediainfo/usePrimaryMediaInfo.tsx | 3 ++- src/types/base/common/shared/types.ts | 3 +++ src/types/cardOptions.ts | 3 ++- src/types/dataAttributes.ts | 5 +++-- src/types/itemDto.ts | 4 ---- 9 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 src/types/base/common/shared/types.ts diff --git a/src/components/cardbuilder/Card/cardHelper.ts b/src/components/cardbuilder/Card/cardHelper.ts index 4ce3247379..ae4ef06f18 100644 --- a/src/components/cardbuilder/Card/cardHelper.ts +++ b/src/components/cardbuilder/Card/cardHelper.ts @@ -16,7 +16,8 @@ import datetime from 'scripts/datetime'; import { isUsingLiveTvNaming } from '../cardBuilderUtils'; -import type { ItemDto, NullableNumber, NullableString } from 'types/itemDto'; +import type { NullableNumber, NullableString } from 'types/base/common/shared/types'; +import type { ItemDto } from 'types/itemDto'; import type { CardOptions } from 'types/cardOptions'; import type { DataAttributes } from 'types/dataAttributes'; import { getDataAttributes } from 'utils/items'; diff --git a/src/components/cardbuilder/Card/useCardImageUrl.ts b/src/components/cardbuilder/Card/useCardImageUrl.ts index 1d1fca2dc7..8afec0cab3 100644 --- a/src/components/cardbuilder/Card/useCardImageUrl.ts +++ b/src/components/cardbuilder/Card/useCardImageUrl.ts @@ -4,7 +4,8 @@ import { getImageApi } from '@jellyfin/sdk/lib/utils/api/image-api'; import { useApi } from 'hooks/useApi'; import { getDesiredAspect } from '../cardBuilderUtils'; import { CardShape } from 'utils/card'; -import type { ItemDto, NullableNumber, NullableString } from 'types/itemDto'; +import type { NullableNumber, NullableString } from 'types/base/common/shared/types'; +import type { ItemDto } from 'types/itemDto'; import type { CardOptions } from 'types/cardOptions'; function getPreferThumbInfo(item: ItemDto, cardOptions: CardOptions) { diff --git a/src/components/cardbuilder/cardBuilderUtils.ts b/src/components/cardbuilder/cardBuilderUtils.ts index ecd1d375b5..489d6c8abd 100644 --- a/src/components/cardbuilder/cardBuilderUtils.ts +++ b/src/components/cardbuilder/cardBuilderUtils.ts @@ -1,4 +1,4 @@ -import { CardShape } from 'utils/card'; +import { CardShape } from '../../utils/card'; import { randomInt } from '../../utils/number'; import classNames from 'classnames'; diff --git a/src/components/indicators/useIndicator.tsx b/src/components/indicators/useIndicator.tsx index f54d76d9cd..da5ea537bf 100644 --- a/src/components/indicators/useIndicator.tsx +++ b/src/components/indicators/useIndicator.tsx @@ -16,7 +16,8 @@ import classNames from 'classnames'; import datetime from 'scripts/datetime'; import itemHelper from 'components/itemHelper'; import AutoTimeProgressBar from 'elements/emby-progressbar/AutoTimeProgressBar'; -import type { ItemDto, NullableString } from 'types/itemDto'; +import type { NullableString } from 'types/base/common/shared/types'; +import type { ItemDto } from 'types/itemDto'; import type { ProgressOptions } from 'types/progressOptions'; const TypeIcon = { diff --git a/src/components/mediainfo/usePrimaryMediaInfo.tsx b/src/components/mediainfo/usePrimaryMediaInfo.tsx index b702bc42ce..6c58609152 100644 --- a/src/components/mediainfo/usePrimaryMediaInfo.tsx +++ b/src/components/mediainfo/usePrimaryMediaInfo.tsx @@ -3,7 +3,8 @@ import * as userSettings from 'scripts/settings/userSettings'; import datetime from 'scripts/datetime'; import globalize from 'scripts/globalize'; import itemHelper from '../itemHelper'; -import type { ItemDto, NullableNumber, NullableString } from 'types/itemDto'; +import type { NullableNumber, NullableString } from 'types/base/common/shared/types'; +import type { ItemDto } from 'types/itemDto'; import type { MiscInfo } from 'types/mediaInfoItem'; function shouldShowFolderRuntime( diff --git a/src/types/base/common/shared/types.ts b/src/types/base/common/shared/types.ts new file mode 100644 index 0000000000..f89240dc23 --- /dev/null +++ b/src/types/base/common/shared/types.ts @@ -0,0 +1,3 @@ +export type NullableString = string | null | undefined; +export type NullableNumber = number | null | undefined; +export type NullableBoolean = boolean | null | undefined; diff --git a/src/types/cardOptions.ts b/src/types/cardOptions.ts index 19cea6a272..b18906c0c4 100644 --- a/src/types/cardOptions.ts +++ b/src/types/cardOptions.ts @@ -4,7 +4,8 @@ import type { UserItemDataDto } from '@jellyfin/sdk/lib/generated-client/models/ import type { BaseItemDtoImageBlurHashes } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto-image-blur-hashes'; import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; import { CardShape } from 'utils/card'; -import type { ItemDto, NullableString } from './itemDto'; +import type { NullableString } from './base/common/shared/types'; +import type { ItemDto } from './itemDto'; import type { ParentId } from './library'; export interface CardOptions { diff --git a/src/types/dataAttributes.ts b/src/types/dataAttributes.ts index 1258d61cd4..b9ae880f7a 100644 --- a/src/types/dataAttributes.ts +++ b/src/types/dataAttributes.ts @@ -1,5 +1,6 @@ -import type { CollectionType, UserItemDataDto } from '@jellyfin/sdk/lib/generated-client'; -import type { NullableBoolean, NullableNumber, NullableString } from './itemDto'; +import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; +import type { UserItemDataDto } from '@jellyfin/sdk/lib/generated-client/models/user-item-data-dto'; +import type { NullableBoolean, NullableNumber, NullableString } from './base/common/shared/types'; export type AttributesOpts = { context?: CollectionType, diff --git a/src/types/itemDto.ts b/src/types/itemDto.ts index 37fc15fd8e..a3fcedeaee 100644 --- a/src/types/itemDto.ts +++ b/src/types/itemDto.ts @@ -20,7 +20,3 @@ export interface ItemDto extends BaseItem, TimerInfo, SeriesTimerInfo, SearchHin 'Name'?: string | null; 'ItemId'?: string | null; } - -export type NullableString = string | null | undefined; -export type NullableNumber = number | null | undefined; -export type NullableBoolean = boolean | null | undefined; From 56e2cffa812a380deb67c9a8df1cc55e383ca516 Mon Sep 17 00:00:00 2001 From: jellyfin-bot Date: Thu, 29 Feb 2024 07:05:45 +0000 Subject: [PATCH 36/90] Update @jellyfin/sdk to 0.0.0-unstable.202402290502 --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7ce9df3f4e..d48b10e846 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@fontsource/noto-sans-kr": "5.0.17", "@fontsource/noto-sans-sc": "5.0.17", "@fontsource/noto-sans-tc": "5.0.17", - "@jellyfin/sdk": "0.0.0-unstable.202402240501", + "@jellyfin/sdk": "0.0.0-unstable.202402290502", "@loadable/component": "5.16.3", "@mui/icons-material": "5.15.5", "@mui/material": "5.15.5", @@ -3638,9 +3638,9 @@ "dev": true }, "node_modules/@jellyfin/sdk": { - "version": "0.0.0-unstable.202402240501", - "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202402240501.tgz", - "integrity": "sha512-UP6ajpwL6TZbCz/zEA6v5aNoDfOzHlIVM6+Z4VhzTroU6zbUIvVjUi67heANWBO2otq0qee6CyVcLC1/vX0B6Q==", + "version": "0.0.0-unstable.202402290502", + "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202402290502.tgz", + "integrity": "sha512-oQSWR2acRT26oBvVntzwGiu5aCGet9ZIEaweEi7Vy0OF9Xl+k9BLlocg5ytcyyKY5ugziGH+j+19D5YHSaAJuA==", "peerDependencies": { "axios": "^1.3.4" } @@ -25225,9 +25225,9 @@ "dev": true }, "@jellyfin/sdk": { - "version": "0.0.0-unstable.202402240501", - "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202402240501.tgz", - "integrity": "sha512-UP6ajpwL6TZbCz/zEA6v5aNoDfOzHlIVM6+Z4VhzTroU6zbUIvVjUi67heANWBO2otq0qee6CyVcLC1/vX0B6Q==", + "version": "0.0.0-unstable.202402290502", + "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202402290502.tgz", + "integrity": "sha512-oQSWR2acRT26oBvVntzwGiu5aCGet9ZIEaweEi7Vy0OF9Xl+k9BLlocg5ytcyyKY5ugziGH+j+19D5YHSaAJuA==", "requires": {} }, "@jest/schemas": { From 9748e78a97e6ae4dbbf35ba983c4e1d1c16d698e Mon Sep 17 00:00:00 2001 From: queeup Date: Thu, 29 Feb 2024 20:50:37 +0000 Subject: [PATCH 37/90] Translated using Weblate (Turkish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/tr/ --- src/strings/tr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/tr.json b/src/strings/tr.json index a67a8e81b5..5ee9b1f733 100644 --- a/src/strings/tr.json +++ b/src/strings/tr.json @@ -1777,5 +1777,6 @@ "HeaderAllRecordings": "Tüm Kayıtlar", "LabelBuildVersion": "Yapı sürümü", "LabelServerVersion": "Sunucu sürümü", - "LabelWebVersion": "Web sürümü" + "LabelWebVersion": "Web sürümü", + "ButtonEditUser": "Kullanıcıyı düzenle" } From 3874ad4f513c641a196d11c910a9c6f6f0156da1 Mon Sep 17 00:00:00 2001 From: Carl C Date: Fri, 1 Mar 2024 14:40:00 +0000 Subject: [PATCH 38/90] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)=20Translation:=20Jellyfin/Jellyfin=20Web=20Tran?= =?UTF-8?q?slate-URL:=20https://translate.jellyfin.org/projects/jellyfin/j?= =?UTF-8?q?ellyfin-web/nb=5FNO/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/strings/nb.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/nb.json b/src/strings/nb.json index cc80ede636..1a6187b5de 100644 --- a/src/strings/nb.json +++ b/src/strings/nb.json @@ -1781,5 +1781,6 @@ "LabelAlbumGain": "Albumjustering", "LabelSelectAudioNormalization": "Lydnormalisering", "LabelTrackGain": "Sporjustering", - "HeaderAllRecordings": "Alle opptak" + "HeaderAllRecordings": "Alle opptak", + "ButtonEditUser": "Rediger bruker" } From 6fcc392c75436ab2cc8636c174459e3894d03cc1 Mon Sep 17 00:00:00 2001 From: nextlooper42 Date: Fri, 1 Mar 2024 15:45:50 +0000 Subject: [PATCH 39/90] Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sk/ --- src/strings/sk.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/sk.json b/src/strings/sk.json index 1b663e025a..578da7806f 100644 --- a/src/strings/sk.json +++ b/src/strings/sk.json @@ -1781,5 +1781,7 @@ "LabelAlbumGain": "Zosilnenie pre album", "LabelSelectAudioNormalization": "Normalizácia hlasitosti", "LabelTrackGain": "Zosilnenie stopy", - "SearchResultsEmpty": "Prepáčte! Nenašli sa žiadne výsledky pre \"{0}\"" + "SearchResultsEmpty": "Prepáčte! Nenašli sa žiadne výsledky pre \"{0}\"", + "ButtonEditUser": "Upraviť používateľa", + "HeaderAllRecordings": "Všetky nahrávky" } From efe5d0b84de612e4928012d16c38030719faaad6 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Fri, 1 Mar 2024 21:15:52 +0300 Subject: [PATCH 40/90] Remove disabled jsx-no-useless-fragment commit Co-authored-by: Bill Thornton --- src/components/cardbuilder/Card/Cards.tsx | 24 ++++++++--------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/components/cardbuilder/Card/Cards.tsx b/src/components/cardbuilder/Card/Cards.tsx index 82b67cbdfc..ca5a52f76e 100644 --- a/src/components/cardbuilder/Card/Cards.tsx +++ b/src/components/cardbuilder/Card/Cards.tsx @@ -10,23 +10,15 @@ interface CardsProps { cardOptions: CardOptions; } -const Cards: FC = ({ - items = [], - cardOptions -}) => { +const Cards: FC = ({ items, cardOptions }) => { setCardData(items, cardOptions); - return ( - // eslint-disable-next-line react/jsx-no-useless-fragment - <> - {items?.map((item) => ( - - ))} - - ); + + const renderCards = () => + items.map((item) => ( + + )); + + return <>{renderCards()}; }; export default Cards; From 7475fb979f76cf72956769eb20df3d8d74ffa9da Mon Sep 17 00:00:00 2001 From: Cyril Date: Sat, 2 Mar 2024 15:42:25 +0000 Subject: [PATCH 41/90] Translated using Weblate (Chinese (Traditional, Hong Kong)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hant_HK/ --- src/strings/zh-hk.json | 73 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/src/strings/zh-hk.json b/src/strings/zh-hk.json index bdb4e50ef6..b8ab955467 100644 --- a/src/strings/zh-hk.json +++ b/src/strings/zh-hk.json @@ -1179,5 +1179,76 @@ "NoNewDevicesFound": "未偵測到調諧器。請關閉此對話框,手動輸入裝置資料。", "OptionEnableForAllTuners": "為所有調諧器啟用", "MessageConfirmDeleteTunerDevice": "你確定要刪除此裝置嗎?", - "LabelDate": "日期" + "LabelDate": "日期", + "LabelDeveloper": "開發者", + "Select": "選擇", + "MediaInfoProfile": "設定檔", + "MessageConfirmShutdown": "你確定要關閉這個服務器嗎?", + "SeriesYearToPresent": "{0} - 現在", + "SubtitleBlue": "藍", + "SaveChanges": "儲存變更", + "RepeatOne": "重複播放一次", + "Yesterday": "昨天", + "ButtonEditUser": "更改用戶", + "Whitelist": "白名單", + "Movie": "電影", + "Settings": "設定", + "OptionMax": "最大", + "Quality": "品質", + "PlayCount": "播放次數", + "SelectServer": "選擇服務器", + "OptionWeekdays": "平日", + "RepeatAll": "重複播放所有", + "ShowYear": "顯示年份", + "NumLocationsValue": "{0} 資料夾", + "PlayFromBeginning": "從頭播放", + "Profile": "設定檔", + "QuickConnect": "快速連結", + "MySubtitles": "我的字幕", + "Subtitles": "字幕", + "Repeat": "重複播放", + "Subtitle": "字幕", + "Rewind": "倒回", + "ResetPassword": "重置密碼", + "SubtitleBlack": "黑", + "SubtitleGray": "灰", + "Bold": "粗體", + "LabelWebVersion": "網站版本", + "SecondarySubtitles": "副字幕", + "Played": "已播放", + "TabPlugins": "插件", + "TagsValue": "標籤: {0}", + "Trailers": "預告片", + "Transcoding": "轉碼", + "Unmute": "取消靜音", + "ValueCodec": "編解碼器: {0}", + "Writer": "編劇", + "Yes": "是", + "OptionWeekends": "周末", + "Print": "列印", + "Share": "分享", + "TabCodecs": "編解碼器", + "Tags": "標籤", + "Series": "系列", + "SearchResults": "搜索結果", + "OptionRandom": "隨機", + "Preview": "預覽", + "Suggestions": "建議", + "SubtitleYellow": "黃", + "Menu": "菜單", + "RememberMe": "保持登入狀態", + "Search": "搜索", + "HeaderAllRecordings": "所有錄影的節目", + "MenuOpen": "打開菜單", + "TitleHardwareAcceleration": "硬件加速", + "Upload": "上傳", + "Watched": "已觀看", + "Writers": "編劇", + "SubtitleRed": "紅", + "SubtitleWhite": "白", + "LabelSystem": "系統", + "ShowMore": "顯示更多", + "YoutubeBadRequest": "錯誤的請求。", + "MillisecondsUnit": "毫秒", + "MediaInfoTitle": "標題" } From d5a775502b7e05bb81893c0f102d50cb4961ca02 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Sun, 3 Mar 2024 01:31:35 +0300 Subject: [PATCH 42/90] Move itemdto to base models folder --- src/components/cardbuilder/Card/Card.tsx | 2 +- src/components/cardbuilder/Card/CardBox.tsx | 2 +- src/components/cardbuilder/Card/CardContent.tsx | 2 +- src/components/cardbuilder/Card/CardFooterText.tsx | 2 +- src/components/cardbuilder/Card/CardHoverMenu.tsx | 2 +- src/components/cardbuilder/Card/CardImageContainer.tsx | 2 +- src/components/cardbuilder/Card/CardInnerFooter.tsx | 2 +- src/components/cardbuilder/Card/CardOuterFooter.tsx | 2 +- src/components/cardbuilder/Card/CardOverlayButtons.tsx | 2 +- src/components/cardbuilder/Card/Cards.tsx | 2 +- src/components/cardbuilder/Card/cardHelper.ts | 2 +- src/components/cardbuilder/Card/useCard.ts | 2 +- src/components/cardbuilder/Card/useCardImageUrl.ts | 2 +- src/components/cardbuilder/Card/useCardText.tsx | 2 +- src/components/common/DefaultIconText.tsx | 2 +- src/components/common/DefaultName.tsx | 2 +- src/components/common/Media.tsx | 2 +- src/components/indicators/useIndicator.tsx | 2 +- src/components/listview/List/List.tsx | 2 +- src/components/listview/List/ListContent.tsx | 2 +- src/components/listview/List/ListImageContainer.tsx | 2 +- src/components/listview/List/ListItemBody.tsx | 2 +- src/components/listview/List/ListViewUserDataButtons.tsx | 2 +- src/components/listview/List/Lists.tsx | 2 +- src/components/listview/List/listHelper.ts | 2 +- src/components/listview/List/useList.ts | 2 +- src/components/listview/List/useListTextlines.tsx | 2 +- src/components/mediainfo/PrimaryMediaInfo.tsx | 2 +- src/components/mediainfo/usePrimaryMediaInfo.tsx | 2 +- src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx | 2 +- src/types/{itemDto.ts => base/models/item-dto.ts} | 0 src/types/cardOptions.ts | 2 +- src/types/listOptions.ts | 2 +- 33 files changed, 32 insertions(+), 32 deletions(-) rename src/types/{itemDto.ts => base/models/item-dto.ts} (100%) diff --git a/src/components/cardbuilder/Card/Card.tsx b/src/components/cardbuilder/Card/Card.tsx index e1718e6459..2b5314d4cc 100644 --- a/src/components/cardbuilder/Card/Card.tsx +++ b/src/components/cardbuilder/Card/Card.tsx @@ -4,7 +4,7 @@ import CardWrapper from './CardWrapper'; import CardBox from './CardBox'; import type { CardOptions } from 'types/cardOptions'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; interface CardProps { item?: ItemDto; diff --git a/src/components/cardbuilder/Card/CardBox.tsx b/src/components/cardbuilder/Card/CardBox.tsx index 07c8bc2d67..a7fd41c0cd 100644 --- a/src/components/cardbuilder/Card/CardBox.tsx +++ b/src/components/cardbuilder/Card/CardBox.tsx @@ -6,7 +6,7 @@ import CardHoverMenu from './CardHoverMenu'; import CardOuterFooter from './CardOuterFooter'; import CardContent from './CardContent'; import { CardShape } from 'utils/card'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { CardOptions } from 'types/cardOptions'; interface CardBoxProps { diff --git a/src/components/cardbuilder/Card/CardContent.tsx b/src/components/cardbuilder/Card/CardContent.tsx index 8ebeb0cb87..eb8ee8a2eb 100644 --- a/src/components/cardbuilder/Card/CardContent.tsx +++ b/src/components/cardbuilder/Card/CardContent.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; import { getDefaultBackgroundClass } from '../cardBuilderUtils'; import CardImageContainer from './CardImageContainer'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { CardOptions } from 'types/cardOptions'; interface CardContentProps { diff --git a/src/components/cardbuilder/Card/CardFooterText.tsx b/src/components/cardbuilder/Card/CardFooterText.tsx index b9bf7bbafd..9dae59b8f6 100644 --- a/src/components/cardbuilder/Card/CardFooterText.tsx +++ b/src/components/cardbuilder/Card/CardFooterText.tsx @@ -3,7 +3,7 @@ import Box from '@mui/material/Box'; import useCardText from './useCardText'; import layoutManager from 'components/layoutManager'; import MoreVertIconButton from '../../common/MoreVertIconButton'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { CardOptions } from 'types/cardOptions'; const shouldShowDetailsMenu = ( diff --git a/src/components/cardbuilder/Card/CardHoverMenu.tsx b/src/components/cardbuilder/Card/CardHoverMenu.tsx index c09e2bad83..b79747034b 100644 --- a/src/components/cardbuilder/Card/CardHoverMenu.tsx +++ b/src/components/cardbuilder/Card/CardHoverMenu.tsx @@ -11,7 +11,7 @@ import FavoriteButton from 'elements/emby-ratingbutton/FavoriteButton'; import PlayArrowIconButton from '../../common/PlayArrowIconButton'; import MoreVertIconButton from '../../common/MoreVertIconButton'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { CardOptions } from 'types/cardOptions'; interface CardHoverMenuProps { diff --git a/src/components/cardbuilder/Card/CardImageContainer.tsx b/src/components/cardbuilder/Card/CardImageContainer.tsx index 8a10b6b330..db609f21e6 100644 --- a/src/components/cardbuilder/Card/CardImageContainer.tsx +++ b/src/components/cardbuilder/Card/CardImageContainer.tsx @@ -7,7 +7,7 @@ import RefreshIndicator from 'elements/emby-itemrefreshindicator/RefreshIndicato import Media from '../../common/Media'; import CardInnerFooter from './CardInnerFooter'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { CardOptions } from 'types/cardOptions'; interface CardImageContainerProps { diff --git a/src/components/cardbuilder/Card/CardInnerFooter.tsx b/src/components/cardbuilder/Card/CardInnerFooter.tsx index 33534e8a9a..e5908adc27 100644 --- a/src/components/cardbuilder/Card/CardInnerFooter.tsx +++ b/src/components/cardbuilder/Card/CardInnerFooter.tsx @@ -1,7 +1,7 @@ import React, { type FC } from 'react'; import classNames from 'classnames'; import CardFooterText from './CardFooterText'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { CardOptions } from 'types/cardOptions'; interface CardInnerFooterProps { diff --git a/src/components/cardbuilder/Card/CardOuterFooter.tsx b/src/components/cardbuilder/Card/CardOuterFooter.tsx index f03dcb8703..3f6380aa9e 100644 --- a/src/components/cardbuilder/Card/CardOuterFooter.tsx +++ b/src/components/cardbuilder/Card/CardOuterFooter.tsx @@ -4,7 +4,7 @@ import { useApi } from 'hooks/useApi'; import { getCardLogoUrl } from './cardHelper'; import CardFooterText from './CardFooterText'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { CardOptions } from 'types/cardOptions'; interface CardOuterFooterProps { diff --git a/src/components/cardbuilder/Card/CardOverlayButtons.tsx b/src/components/cardbuilder/Card/CardOverlayButtons.tsx index 66abd459e3..a00b194587 100644 --- a/src/components/cardbuilder/Card/CardOverlayButtons.tsx +++ b/src/components/cardbuilder/Card/CardOverlayButtons.tsx @@ -7,7 +7,7 @@ import { appRouter } from 'components/router/appRouter'; import PlayArrowIconButton from '../../common/PlayArrowIconButton'; import MoreVertIconButton from '../../common/MoreVertIconButton'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { CardOptions } from 'types/cardOptions'; const sholudShowOverlayPlayButton = ( diff --git a/src/components/cardbuilder/Card/Cards.tsx b/src/components/cardbuilder/Card/Cards.tsx index ca5a52f76e..2ea6863954 100644 --- a/src/components/cardbuilder/Card/Cards.tsx +++ b/src/components/cardbuilder/Card/Cards.tsx @@ -1,7 +1,7 @@ import React, { type FC } from 'react'; import { setCardData } from '../cardBuilder'; import Card from './Card'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { CardOptions } from 'types/cardOptions'; import '../card.scss'; diff --git a/src/components/cardbuilder/Card/cardHelper.ts b/src/components/cardbuilder/Card/cardHelper.ts index ae4ef06f18..ab40841073 100644 --- a/src/components/cardbuilder/Card/cardHelper.ts +++ b/src/components/cardbuilder/Card/cardHelper.ts @@ -17,7 +17,7 @@ import datetime from 'scripts/datetime'; import { isUsingLiveTvNaming } from '../cardBuilderUtils'; import type { NullableNumber, NullableString } from 'types/base/common/shared/types'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { CardOptions } from 'types/cardOptions'; import type { DataAttributes } from 'types/dataAttributes'; import { getDataAttributes } from 'utils/items'; diff --git a/src/components/cardbuilder/Card/useCard.ts b/src/components/cardbuilder/Card/useCard.ts index 80c5dd69b7..6c031afa8d 100644 --- a/src/components/cardbuilder/Card/useCard.ts +++ b/src/components/cardbuilder/Card/useCard.ts @@ -9,7 +9,7 @@ import { getDataAttributes } from 'utils/items'; import { CardShape } from 'utils/card'; import layoutManager from 'components/layoutManager'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { CardOptions } from 'types/cardOptions'; interface UseCardProps { diff --git a/src/components/cardbuilder/Card/useCardImageUrl.ts b/src/components/cardbuilder/Card/useCardImageUrl.ts index 8afec0cab3..4675c1d80b 100644 --- a/src/components/cardbuilder/Card/useCardImageUrl.ts +++ b/src/components/cardbuilder/Card/useCardImageUrl.ts @@ -5,7 +5,7 @@ import { useApi } from 'hooks/useApi'; import { getDesiredAspect } from '../cardBuilderUtils'; import { CardShape } from 'utils/card'; import type { NullableNumber, NullableString } from 'types/base/common/shared/types'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { CardOptions } from 'types/cardOptions'; function getPreferThumbInfo(item: ItemDto, cardOptions: CardOptions) { diff --git a/src/components/cardbuilder/Card/useCardText.tsx b/src/components/cardbuilder/Card/useCardText.tsx index 904777fe85..ff93662841 100644 --- a/src/components/cardbuilder/Card/useCardText.tsx +++ b/src/components/cardbuilder/Card/useCardText.tsx @@ -5,7 +5,7 @@ import layoutManager from 'components/layoutManager'; import CardText from './CardText'; import { getCardTextLines } from './cardHelper'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { CardOptions } from 'types/cardOptions'; const enableRightMargin = ( diff --git a/src/components/common/DefaultIconText.tsx b/src/components/common/DefaultIconText.tsx index f6af526c07..c6ea81b18e 100644 --- a/src/components/common/DefaultIconText.tsx +++ b/src/components/common/DefaultIconText.tsx @@ -3,7 +3,7 @@ import React, { type FC } from 'react'; import Icon from '@mui/material/Icon'; import imageHelper from 'utils/image'; import DefaultName from './DefaultName'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; interface DefaultIconTextProps { item: ItemDto; diff --git a/src/components/common/DefaultName.tsx b/src/components/common/DefaultName.tsx index ba782e1162..44a7829531 100644 --- a/src/components/common/DefaultName.tsx +++ b/src/components/common/DefaultName.tsx @@ -2,7 +2,7 @@ import React, { type FC } from 'react'; import Box from '@mui/material/Box'; import itemHelper from 'components/itemHelper'; import { isUsingLiveTvNaming } from '../cardbuilder/cardBuilderUtils'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; interface DefaultNameProps { item: ItemDto; diff --git a/src/components/common/Media.tsx b/src/components/common/Media.tsx index 598c9ec7a5..99858356c0 100644 --- a/src/components/common/Media.tsx +++ b/src/components/common/Media.tsx @@ -2,7 +2,7 @@ import { BaseItemKind, ImageType } from '@jellyfin/sdk/lib/generated-client'; import React, { type FC } from 'react'; import Image from './Image'; import DefaultIconText from './DefaultIconText'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; interface MediaProps { item: ItemDto; diff --git a/src/components/indicators/useIndicator.tsx b/src/components/indicators/useIndicator.tsx index da5ea537bf..a9ecc12bc8 100644 --- a/src/components/indicators/useIndicator.tsx +++ b/src/components/indicators/useIndicator.tsx @@ -17,7 +17,7 @@ import datetime from 'scripts/datetime'; import itemHelper from 'components/itemHelper'; import AutoTimeProgressBar from 'elements/emby-progressbar/AutoTimeProgressBar'; import type { NullableString } from 'types/base/common/shared/types'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { ProgressOptions } from 'types/progressOptions'; const TypeIcon = { diff --git a/src/components/listview/List/List.tsx b/src/components/listview/List/List.tsx index feafd5a04d..8afe3503ba 100644 --- a/src/components/listview/List/List.tsx +++ b/src/components/listview/List/List.tsx @@ -2,7 +2,7 @@ import React, { type FC } from 'react'; import useList from './useList'; import ListContent from './ListContent'; import ListWrapper from './ListWrapper'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { ListOptions } from 'types/listOptions'; import '../../mediainfo/mediainfo.scss'; import '../../guide/programs.scss'; diff --git a/src/components/listview/List/ListContent.tsx b/src/components/listview/List/ListContent.tsx index 0cf2160ca6..6dba901dbf 100644 --- a/src/components/listview/List/ListContent.tsx +++ b/src/components/listview/List/ListContent.tsx @@ -10,7 +10,7 @@ import ListItemBody from './ListItemBody'; import ListImageContainer from './ListImageContainer'; import ListViewUserDataButtons from './ListViewUserDataButtons'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { ListOptions } from 'types/listOptions'; interface ListContentProps { diff --git a/src/components/listview/List/ListImageContainer.tsx b/src/components/listview/List/ListImageContainer.tsx index b447b2a701..bebe97cb12 100644 --- a/src/components/listview/List/ListImageContainer.tsx +++ b/src/components/listview/List/ListImageContainer.tsx @@ -13,7 +13,7 @@ import { import Media from 'components/common/Media'; import PlayArrowIconButton from 'components/common/PlayArrowIconButton'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { ListOptions } from 'types/listOptions'; interface ListImageContainerProps { diff --git a/src/components/listview/List/ListItemBody.tsx b/src/components/listview/List/ListItemBody.tsx index 5152040585..847d46b4de 100644 --- a/src/components/listview/List/ListItemBody.tsx +++ b/src/components/listview/List/ListItemBody.tsx @@ -4,7 +4,7 @@ import Box from '@mui/material/Box'; import useListTextlines from './useListTextlines'; import PrimaryMediaInfo from '../../mediainfo/PrimaryMediaInfo'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { ListOptions } from 'types/listOptions'; interface ListItemBodyProps { diff --git a/src/components/listview/List/ListViewUserDataButtons.tsx b/src/components/listview/List/ListViewUserDataButtons.tsx index 97668ed999..8a8b4ce901 100644 --- a/src/components/listview/List/ListViewUserDataButtons.tsx +++ b/src/components/listview/List/ListViewUserDataButtons.tsx @@ -8,7 +8,7 @@ import InfoIconButton from '../../common/InfoIconButton'; import RightIconButtons from '../../common/RightIconButtons'; import MoreVertIconButton from '../../common/MoreVertIconButton'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { ListOptions } from 'types/listOptions'; interface ListViewUserDataButtonsProps { diff --git a/src/components/listview/List/Lists.tsx b/src/components/listview/List/Lists.tsx index 1516140635..1215851cc5 100644 --- a/src/components/listview/List/Lists.tsx +++ b/src/components/listview/List/Lists.tsx @@ -5,7 +5,7 @@ import { getIndex } from './listHelper'; import ListGroupHeaderWrapper from './ListGroupHeaderWrapper'; import List from './List'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { ListOptions } from 'types/listOptions'; import '../listview.scss'; diff --git a/src/components/listview/List/listHelper.ts b/src/components/listview/List/listHelper.ts index 8386ff3273..d909feb568 100644 --- a/src/components/listview/List/listHelper.ts +++ b/src/components/listview/List/listHelper.ts @@ -3,7 +3,7 @@ import { BaseItemKind, ImageType } from '@jellyfin/sdk/lib/generated-client'; import { getImageApi } from '@jellyfin/sdk/lib/utils/api/image-api'; import globalize from 'scripts/globalize'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { ListOptions } from 'types/listOptions'; const sortBySortName = (item: ItemDto): string => { diff --git a/src/components/listview/List/useList.ts b/src/components/listview/List/useList.ts index 75a60c6b54..196721a0dc 100644 --- a/src/components/listview/List/useList.ts +++ b/src/components/listview/List/useList.ts @@ -2,7 +2,7 @@ import classNames from 'classnames'; import { getDataAttributes } from 'utils/items'; import layoutManager from 'components/layoutManager'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { ListOptions } from 'types/listOptions'; interface UseListProps { diff --git a/src/components/listview/List/useListTextlines.tsx b/src/components/listview/List/useListTextlines.tsx index da66673a5a..490f94634d 100644 --- a/src/components/listview/List/useListTextlines.tsx +++ b/src/components/listview/List/useListTextlines.tsx @@ -3,7 +3,7 @@ import React from 'react'; import itemHelper from '../../itemHelper'; import datetime from 'scripts/datetime'; import ListTextWrapper from './ListTextWrapper'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { ListOptions } from 'types/listOptions'; function getParentTitle( diff --git a/src/components/mediainfo/PrimaryMediaInfo.tsx b/src/components/mediainfo/PrimaryMediaInfo.tsx index 2978a41683..c68f823b9b 100644 --- a/src/components/mediainfo/PrimaryMediaInfo.tsx +++ b/src/components/mediainfo/PrimaryMediaInfo.tsx @@ -8,7 +8,7 @@ import StarIcons from './StarIcons'; import CaptionMediaInfo from './CaptionMediaInfo'; import CriticRatingMediaInfo from './CriticRatingMediaInfo'; import EndsAt from './EndsAt'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { MiscInfo } from 'types/mediaInfoItem'; interface PrimaryMediaInfoProps { diff --git a/src/components/mediainfo/usePrimaryMediaInfo.tsx b/src/components/mediainfo/usePrimaryMediaInfo.tsx index 6c58609152..d41eea0ec3 100644 --- a/src/components/mediainfo/usePrimaryMediaInfo.tsx +++ b/src/components/mediainfo/usePrimaryMediaInfo.tsx @@ -4,7 +4,7 @@ import datetime from 'scripts/datetime'; import globalize from 'scripts/globalize'; import itemHelper from '../itemHelper'; import type { NullableNumber, NullableString } from 'types/base/common/shared/types'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; import type { MiscInfo } from 'types/mediaInfoItem'; function shouldShowFolderRuntime( diff --git a/src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx b/src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx index 67a65703dc..8d2541f9e3 100644 --- a/src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx +++ b/src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx @@ -10,7 +10,7 @@ import Typography from '@mui/material/Typography'; import Box from '@mui/material/Box'; import { toPercent } from 'utils/number'; import { getCurrentDateTimeLocale } from 'scripts/globalize'; -import type { ItemDto } from 'types/itemDto'; +import type { ItemDto } from 'types/base/models/item-dto'; function CircularProgressWithLabel( props: CircularProgressProps & { value: number } diff --git a/src/types/itemDto.ts b/src/types/base/models/item-dto.ts similarity index 100% rename from src/types/itemDto.ts rename to src/types/base/models/item-dto.ts diff --git a/src/types/cardOptions.ts b/src/types/cardOptions.ts index b18906c0c4..02d10f965f 100644 --- a/src/types/cardOptions.ts +++ b/src/types/cardOptions.ts @@ -5,7 +5,7 @@ import type { BaseItemDtoImageBlurHashes } from '@jellyfin/sdk/lib/generated-cli import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; import { CardShape } from 'utils/card'; import type { NullableString } from './base/common/shared/types'; -import type { ItemDto } from './itemDto'; +import type { ItemDto } from './base/models/item-dto'; import type { ParentId } from './library'; export interface CardOptions { diff --git a/src/types/listOptions.ts b/src/types/listOptions.ts index b9b5eea80a..e34999e758 100644 --- a/src/types/listOptions.ts +++ b/src/types/listOptions.ts @@ -1,6 +1,6 @@ import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; -import type { ItemDto } from './itemDto'; +import type { ItemDto } from './base/models/item-dto'; export interface ListOptions { items?: ItemDto[] | null; index?: string; From 064cebd41f58b3752b7cddef14e5438d63a245e3 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Sun, 3 Mar 2024 01:33:51 +0300 Subject: [PATCH 43/90] Add shared ItemKind Type --- src/types/base/models/item-kind.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/types/base/models/item-kind.ts diff --git a/src/types/base/models/item-kind.ts b/src/types/base/models/item-kind.ts new file mode 100644 index 0000000000..d89f8cbeef --- /dev/null +++ b/src/types/base/models/item-kind.ts @@ -0,0 +1,12 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; + +export const ItemKind = { + ...BaseItemKind, + Timer: 'Timer', + SeriesTimer: 'SeriesTimer' +} as const; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type ItemKind = keyof typeof ItemKind; + +export type ItemType = ItemKind | null | undefined; From 28bf7d4dfbd56e7301829276eba47ada845250ec Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Sun, 3 Mar 2024 01:36:21 +0300 Subject: [PATCH 44/90] Add shared ItemMediaKind Type --- src/types/base/models/item-media-kind.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/types/base/models/item-media-kind.ts diff --git a/src/types/base/models/item-media-kind.ts b/src/types/base/models/item-media-kind.ts new file mode 100644 index 0000000000..14fbe6a137 --- /dev/null +++ b/src/types/base/models/item-media-kind.ts @@ -0,0 +1,15 @@ +export const ItemMediaKind = { + MusicArtist: 'MusicArtist', + Playlist: 'Playlist', + MusicGenre: 'MusicGenre', + Photo: 'Photo', + Audio: 'Audio', + Video: 'Video', + Book: 'Book', + Recording: 'Recording' +} as const; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type ItemMediaKind = keyof typeof ItemMediaKind; + +export type ItemMediaType = ItemMediaKind | null | undefined; From ebefdc64c58e04ce97d00a756ae2fc6f4c27c7dc Mon Sep 17 00:00:00 2001 From: felix920506 Date: Sat, 2 Mar 2024 23:58:57 +0000 Subject: [PATCH 45/90] Translated using Weblate (Chinese (Traditional)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hant/ --- src/strings/zh-tw.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strings/zh-tw.json b/src/strings/zh-tw.json index 27c659bfe9..b0931adb39 100644 --- a/src/strings/zh-tw.json +++ b/src/strings/zh-tw.json @@ -203,7 +203,7 @@ "WelcomeToProject": "歡迎使用 Jellyfin!", "WizardCompleted": "這就是我們所需的全部資訊,Jellyfin 現在正在收集你的媒體櫃的資料,在這段時間內,不妨參考我們推出的應用程式。按一下完成進入控制台。", "Actor": "演員", - "AddToPlayQueue": "加入至播放佇列", + "AddToPlayQueue": "加入播放佇列", "AddToPlaylist": "加入播放清單", "Absolute": "絕對", "AccessRestrictedTryAgainLater": "目前存取受限,請稍後再試。", @@ -887,7 +887,7 @@ "PerfectMatch": "最佳配對", "PictureInPicture": "浮窗播放", "PlayFromBeginning": "從頭開始播放", - "PlayNext": "播放下一個", + "PlayNext": "下一個播放", "Next": "下一個", "OptionCaptionInfoExSamsung": "CaptionInfoEx(三星)", "People": "人物", From 468bc345dc3ed15ff59f0b62db263a4a8ade9dfc Mon Sep 17 00:00:00 2001 From: GuillomoLavallo Date: Sun, 3 Mar 2024 02:20:55 +0000 Subject: [PATCH 46/90] Translated using Weblate (Spanish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es/ --- src/strings/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/es.json b/src/strings/es.json index 09b8745099..26333bf443 100644 --- a/src/strings/es.json +++ b/src/strings/es.json @@ -1782,5 +1782,6 @@ "LabelAlbumGain": "Ganancia de Álbum", "LabelSelectAudioNormalization": "Normalización de audio", "LabelTrackGain": "Ganancia de pista", - "HeaderAllRecordings": "Todas las grabaciones" + "HeaderAllRecordings": "Todas las grabaciones", + "ButtonEditUser": "Editar usuario" } From 8fba758037b3679c583b57283d233d4d0f6e0515 Mon Sep 17 00:00:00 2001 From: kaboom83 Date: Sun, 3 Mar 2024 02:08:19 +0000 Subject: [PATCH 47/90] Translated using Weblate (Slovenian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sl/ --- src/strings/sl-si.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/strings/sl-si.json b/src/strings/sl-si.json index a74186d5f7..f7d996710c 100644 --- a/src/strings/sl-si.json +++ b/src/strings/sl-si.json @@ -10,7 +10,7 @@ "HeaderUser": "Uporabnik", "LabelArtists": "Izvajalci", "LabelContentType": "Tip vsebine", - "LabelCountry": "Država", + "LabelCountry": "Država/Regija", "LabelCurrentPassword": "Trenutno geslo", "LabelFinish": "Zaključi", "LabelLanguage": "Jezik", @@ -1453,7 +1453,7 @@ "LabelSelectAudioChannels": "Kanali", "LabelAllowedAudioChannels": "Največje število dovoljenih zvočnih kanalov", "AllowHevcEncoding": "Dovoli kodiranje v HEVC format", - "PreferFmp4HlsContainerHelp": "Prioritiziraj uporabo fMP4 kot privzeti kontejner za HLS. Omogoča neposredno pretakanje HEVC vsebin na podprtih napravah.", + "PreferFmp4HlsContainerHelp": "Prioritiziraj uporabo fMP4 kot privzeti kontejner za HLS. Omogoča neposredno pretakanje HEVC in AV1 vsebin na podprtih napravah.", "PreferFmp4HlsContainer": "Prioritiziraj fMP4-HLS kontejner", "LabelSyncPlayInfo": "Informacije o SyncPlay", "LabelOriginalMediaInfo": "Informacije o izvorni predstavnosti", @@ -1770,5 +1770,7 @@ "SubtitleLightGray": "Svetlo siva", "SubtitleMagenta": "Magenta", "LabelParallelImageEncodingLimitHelp": "Največje dovoljeno število vzporednih kodiranj slik. Nastavite na 0 za samodejno omejitev glede na zmogljivost vašega sistema.", - "AiTranslated": "AI prevedeno" + "AiTranslated": "AI prevedeno", + "ButtonEditUser": "Uredi uporabnika", + "HeaderAllRecordings": "Vsi posnetki" } From 849bfb17e39581d4864e8f181c0225850e15836e Mon Sep 17 00:00:00 2001 From: felix920506 Date: Sun, 3 Mar 2024 03:13:14 +0000 Subject: [PATCH 48/90] Translated using Weblate (Chinese (Traditional)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hant/ --- src/strings/zh-tw.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/zh-tw.json b/src/strings/zh-tw.json index b0931adb39..adbcc220af 100644 --- a/src/strings/zh-tw.json +++ b/src/strings/zh-tw.json @@ -757,7 +757,7 @@ "No": "不要", "Off": "關閉", "OptionAdminUsers": "管理員", - "OptionAllowRemoteControlOthers": "允許其他使用者遠端控制", + "OptionAllowRemoteControlOthers": "允許遠端控制其他用戶的裝置", "OptionReleaseDate": "釋出日期", "OptionWeekends": "假日", "PlayNextEpisodeAutomatically": "自動播放下一集", From e65c71015f74f6b92d9687cdd9c3db41d4fc734f Mon Sep 17 00:00:00 2001 From: christianwell Date: Sun, 3 Mar 2024 19:44:48 +0000 Subject: [PATCH 49/90] Translated using Weblate (Danish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/da/ --- src/strings/da.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/strings/da.json b/src/strings/da.json index 86af18d6f0..276aff793d 100644 --- a/src/strings/da.json +++ b/src/strings/da.json @@ -346,7 +346,7 @@ "LabelCollection": "Samling", "LabelCommunityRating": "Fællesskabsvurdering", "LabelContentType": "Indholdstype", - "LabelCountry": "Land", + "LabelCountry": "Land/Region", "LabelCriticRating": "Kritiker bedømmelse", "LabelCurrentPassword": "Nuværende kode", "LabelCustomCertificatePath": "Brugerdefineret SSL certifikat sti", @@ -1754,5 +1754,12 @@ "LabelLevel": "Niveau", "LabelMediaDetails": "Media detaljer", "MenuClose": "Luk menu", - "MediaInfoDvBlSignalCompatibilityId": "DV bl signals kompatibilitets id" + "MediaInfoDvBlSignalCompatibilityId": "DV bl signals kompatibilitets id", + "ButtonEditUser": "Rediger bruger", + "HeaderAllRecordings": "alle optagelser", + "ListView": "liste udsigt", + "LabelVppTonemappingContrastHelp": "Tilføj kontrastere", + "LogLevel.Trace": "Spore", + "LabelServerVersion": "server version", + "LabelWebVersion": "Web version" } From 655cfbe3cabbd2bd4fde447861626b23a582a6e9 Mon Sep 17 00:00:00 2001 From: Alvin Date: Mon, 4 Mar 2024 03:05:10 +0000 Subject: [PATCH 50/90] Translated using Weblate (Swedish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sv/ --- src/strings/sv.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/strings/sv.json b/src/strings/sv.json index 588f72ee3a..1110ab7ae8 100644 --- a/src/strings/sv.json +++ b/src/strings/sv.json @@ -415,7 +415,7 @@ "LabelCollection": "Samling", "LabelCommunityRating": "Användaromdöme", "LabelContentType": "Innehållstyp", - "LabelCountry": "Land", + "LabelCountry": "Land/Region", "LabelCriticRating": "Kritikerbetyg", "LabelCurrentPassword": "Nuvarande lösenord", "LabelCustomCertificatePath": "Sökväg för anpassat SSL-certifikat", @@ -1781,5 +1781,9 @@ "LabelAlbumGain": "Albumförstärkning", "LabelSelectAudioNormalization": "Ljudnormalisering", "LabelTrackGain": "Spårförstärkning", - "HeaderAllRecordings": "Alla inspelningar" + "HeaderAllRecordings": "Alla inspelningar", + "LabelBuildVersion": "Byggversion", + "ButtonEditUser": "redigera användare", + "LabelServerVersion": "Serverversion", + "LabelWebVersion": "Webbversion" } From d70b4f7979cabfac437c94f845eb2b7e7e81215d Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Mon, 4 Mar 2024 09:28:06 -0500 Subject: [PATCH 51/90] Keep exact sdk version in package.json --- .github/workflows/update-sdk.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/update-sdk.yml b/.github/workflows/update-sdk.yml index a6cbff71ef..50ba172cfa 100644 --- a/.github/workflows/update-sdk.yml +++ b/.github/workflows/update-sdk.yml @@ -33,7 +33,6 @@ jobs: npm i --save @jellyfin/sdk@unstable VERSION=$(jq -r '.dependencies["@jellyfin/sdk"]' package.json) echo "JF_SDK_VERSION=${VERSION}" >> $GITHUB_ENV - git checkout package.json - name: Open a pull request uses: peter-evans/create-pull-request@v6 From 5990c3e5409013e644f412a15166c501797a0aef Mon Sep 17 00:00:00 2001 From: jellyfin-bot Date: Mon, 4 Mar 2024 15:05:37 +0000 Subject: [PATCH 52/90] Update @jellyfin/sdk to 0.0.0-unstable.202403040506 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index d48b10e846..9aa9b1c064 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@fontsource/noto-sans-kr": "5.0.17", "@fontsource/noto-sans-sc": "5.0.17", "@fontsource/noto-sans-tc": "5.0.17", - "@jellyfin/sdk": "0.0.0-unstable.202402290502", + "@jellyfin/sdk": "0.0.0-unstable.202403040506", "@loadable/component": "5.16.3", "@mui/icons-material": "5.15.5", "@mui/material": "5.15.5", @@ -3638,9 +3638,9 @@ "dev": true }, "node_modules/@jellyfin/sdk": { - "version": "0.0.0-unstable.202402290502", - "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202402290502.tgz", - "integrity": "sha512-oQSWR2acRT26oBvVntzwGiu5aCGet9ZIEaweEi7Vy0OF9Xl+k9BLlocg5ytcyyKY5ugziGH+j+19D5YHSaAJuA==", + "version": "0.0.0-unstable.202403040506", + "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202403040506.tgz", + "integrity": "sha512-B+i1Mpn10AC0Ks+PIVXCRcpuy7E50bsPy9AYmEt2rgfOpnAJ9qjGqyFligGI8l391NMtv/p0XJd7NUsApHU1Lg==", "peerDependencies": { "axios": "^1.3.4" } @@ -25225,9 +25225,9 @@ "dev": true }, "@jellyfin/sdk": { - "version": "0.0.0-unstable.202402290502", - "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202402290502.tgz", - "integrity": "sha512-oQSWR2acRT26oBvVntzwGiu5aCGet9ZIEaweEi7Vy0OF9Xl+k9BLlocg5ytcyyKY5ugziGH+j+19D5YHSaAJuA==", + "version": "0.0.0-unstable.202403040506", + "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202403040506.tgz", + "integrity": "sha512-B+i1Mpn10AC0Ks+PIVXCRcpuy7E50bsPy9AYmEt2rgfOpnAJ9qjGqyFligGI8l391NMtv/p0XJd7NUsApHU1Lg==", "requires": {} }, "@jest/schemas": { diff --git a/package.json b/package.json index 0eddde68b1..bec653acfd 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@fontsource/noto-sans-kr": "5.0.17", "@fontsource/noto-sans-sc": "5.0.17", "@fontsource/noto-sans-tc": "5.0.17", - "@jellyfin/sdk": "unstable", + "@jellyfin/sdk": "0.0.0-unstable.202403040506", "@loadable/component": "5.16.3", "@mui/icons-material": "5.15.5", "@mui/material": "5.15.5", From 3994486bf432a0efcab2a90f6035a3ce38be94cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:51:54 +0000 Subject: [PATCH 53/90] Bump es5-ext from 0.10.53 to 0.10.64 Bumps [es5-ext](https://github.com/medikoo/es5-ext) from 0.10.53 to 0.10.64. - [Release notes](https://github.com/medikoo/es5-ext/releases) - [Changelog](https://github.com/medikoo/es5-ext/blob/main/CHANGELOG.md) - [Commits](https://github.com/medikoo/es5-ext/compare/v0.10.53...v0.10.64) --- updated-dependencies: - dependency-name: es5-ext dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 79 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9aa9b1c064..0b8fffccca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8388,13 +8388,18 @@ } }, "node_modules/es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" } }, "node_modules/es6-iterator": { @@ -9164,6 +9169,25 @@ "node": ">=8" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esniff/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -13396,9 +13420,9 @@ "dev": true }, "node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "node_modules/no-case": { "version": "3.0.4", @@ -28693,13 +28717,14 @@ } }, "es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" } }, "es6-iterator": { @@ -29271,6 +29296,24 @@ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true }, + "esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "dependencies": { + "type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + } + } + }, "espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -32424,9 +32467,9 @@ "dev": true }, "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "no-case": { "version": "3.0.4", From 671c749157237f86a5b9fa41ceaf5ceb40b153fa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:52:21 +0000 Subject: [PATCH 54/90] Update material-ui monorepo --- package-lock.json | 266 +++++++++++++++++++++++----------------------- package.json | 6 +- 2 files changed, 136 insertions(+), 136 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9aa9b1c064..34349ee75c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,9 +19,9 @@ "@fontsource/noto-sans-tc": "5.0.17", "@jellyfin/sdk": "0.0.0-unstable.202403040506", "@loadable/component": "5.16.3", - "@mui/icons-material": "5.15.5", - "@mui/material": "5.15.5", - "@mui/x-data-grid": "6.18.7", + "@mui/icons-material": "5.15.11", + "@mui/material": "5.15.11", + "@mui/x-data-grid": "6.19.5", "@react-hook/resize-observer": "1.2.6", "@tanstack/react-query": "4.36.1", "@tanstack/react-query-devtools": "4.36.1", @@ -1908,9 +1908,9 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", - "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", + "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -3541,28 +3541,28 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.3.tgz", - "integrity": "sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", "dependencies": { - "@floating-ui/utils": "^0.2.0" + "@floating-ui/utils": "^0.2.1" } }, "node_modules/@floating-ui/dom": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.4.tgz", - "integrity": "sha512-jByEsHIY+eEdCjnTVu+E3ephzTOzkQ8hgUfGwos+bg7NlH33Zc5uO+QHz1mrQUOgIKKDD1RtS201P9NvAfq3XQ==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", "dependencies": { - "@floating-ui/core": "^1.5.3", + "@floating-ui/core": "^1.0.0", "@floating-ui/utils": "^0.2.0" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.6.tgz", - "integrity": "sha512-IB8aCRFxr8nFkdYZgH+Otd9EVQPJoynxeFRGTB8voPoZMRWo8XjYuCRgpI1btvuKY69XMiLnW+ym7zoBHM90Rw==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", "dependencies": { - "@floating-ui/dom": "^1.5.4" + "@floating-ui/dom": "^1.6.1" }, "peerDependencies": { "react": ">=16.8.0", @@ -3816,14 +3816,14 @@ } }, "node_modules/@mui/base": { - "version": "5.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.32.tgz", - "integrity": "sha512-4VptvYeLUYMJhZapWBkD50GmKfOc0XT381KJcTK3ncZYIl8MdBhpR6l8jOyeP5cixUPBJhstjrnmQEAHjCLriw==", + "version": "5.0.0-beta.37", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.37.tgz", + "integrity": "sha512-/o3anbb+DeCng8jNsd3704XtmmLDZju1Fo8R2o7ugrVtPQ/QpcqddwKNzKPZwa0J5T8YNW3ZVuHyQgbTnQLisQ==", "dependencies": { - "@babel/runtime": "^7.23.8", - "@floating-ui/react-dom": "^2.0.5", + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.5", + "@mui/utils": "^5.15.11", "@popperjs/core": "^2.11.8", "clsx": "^2.1.0", "prop-types": "^15.8.1" @@ -3847,20 +3847,20 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.6.tgz", - "integrity": "sha512-0aoWS4qvk1uzm9JBs83oQmIMIQeTBUeqqu8u+3uo2tMznrB5fIKqQVCbCgq+4Tm4jG+5F7dIvnjvQ2aV7UKtdw==", + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.11.tgz", + "integrity": "sha512-JVrJ9Jo4gyU707ujnRzmE8ABBWpXd6FwL9GYULmwZRtfPg89ggXs/S3MStQkpJ1JRWfdLL6S5syXmgQGq5EDAw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { - "version": "5.15.5", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.5.tgz", - "integrity": "sha512-qiql0fd1JY7TZ1wm1RldvU7sL8QUatE9OC12i/qm5rnm/caTFyAfOyTIR7qqxorsJvoZGyrzwoMkal6Ij9kM0A==", + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.11.tgz", + "integrity": "sha512-R5ZoQqnKpd+5Ew7mBygTFLxgYsQHPhgR3TDXSgIHYIjGzYuyPLmGLSdcPUoMdi6kxiYqHlpPj4NJxlbaFD0UHA==", "dependencies": { - "@babel/runtime": "^7.23.8" + "@babel/runtime": "^7.23.9" }, "engines": { "node": ">=12.0.0" @@ -3881,19 +3881,19 @@ } }, "node_modules/@mui/material": { - "version": "5.15.5", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.5.tgz", - "integrity": "sha512-2KfA39f/UWeQl0O22UJs3x1nG3chYlyu9wnux5vTnxUTLzkgYIzQIHaH+ZOGpv5JiZBMKktAPNfhqyhSaQ49qQ==", + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.11.tgz", + "integrity": "sha512-FA3eEuEZaDaxgN3CgfXezMWbCZ4VCeU/sv0F0/PK5n42qIgsPVD6q+j71qS7/62sp6wRFMHtDMpXRlN+tT/7NA==", "dependencies": { - "@babel/runtime": "^7.23.8", - "@mui/base": "5.0.0-beta.32", - "@mui/core-downloads-tracker": "^5.15.5", - "@mui/system": "^5.15.5", + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.37", + "@mui/core-downloads-tracker": "^5.15.11", + "@mui/system": "^5.15.11", "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.5", + "@mui/utils": "^5.15.11", "@types/react-transition-group": "^4.4.10", "clsx": "^2.1.0", - "csstype": "^3.1.2", + "csstype": "^3.1.3", "prop-types": "^15.8.1", "react-is": "^18.2.0", "react-transition-group": "^4.4.5" @@ -3930,12 +3930,12 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/@mui/private-theming": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.6.tgz", - "integrity": "sha512-ZBX9E6VNUSscUOtU8uU462VvpvBS7eFl5VfxAzTRVQBHflzL+5KtnGrebgf6Nd6cdvxa1o0OomiaxSKoN2XDmg==", + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.11.tgz", + "integrity": "sha512-jY/696SnSxSzO1u86Thym7ky5T9CgfidU3NFJjguldqK4f3Z5S97amZ6nffg8gTD0HBjY9scB+4ekqDEUmxZOA==", "dependencies": { - "@babel/runtime": "^7.23.8", - "@mui/utils": "^5.15.6", + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.15.11", "prop-types": "^15.8.1" }, "engines": { @@ -3956,13 +3956,13 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.6.tgz", - "integrity": "sha512-KAn8P8xP/WigFKMlEYUpU9z2o7jJnv0BG28Qu1dhNQVutsLVIFdRf5Nb+0ijp2qgtcmygQ0FtfRuXv5LYetZTg==", + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.11.tgz", + "integrity": "sha512-So21AhAngqo07ces4S/JpX5UaMU2RHXpEA6hNzI6IQjd/1usMPxpgK8wkGgTe3JKmC2KDmH8cvoycq5H3Ii7/w==", "dependencies": { - "@babel/runtime": "^7.23.8", + "@babel/runtime": "^7.23.9", "@emotion/cache": "^11.11.0", - "csstype": "^3.1.2", + "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { @@ -3987,17 +3987,17 @@ } }, "node_modules/@mui/system": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.6.tgz", - "integrity": "sha512-J01D//u8IfXvaEHMBQX5aO2l7Q+P15nt96c4NskX7yp5/+UuZP8XCQJhtBtLuj+M2LLyXHYGmCPeblsmmscP2Q==", + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.11.tgz", + "integrity": "sha512-9j35suLFq+MgJo5ktVSHPbkjDLRMBCV17NMBdEQurh6oWyGnLM4uhU4QGZZQ75o0vuhjJghOCA1jkO3+79wKsA==", "dependencies": { - "@babel/runtime": "^7.23.8", - "@mui/private-theming": "^5.15.6", - "@mui/styled-engine": "^5.15.6", + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.15.11", + "@mui/styled-engine": "^5.15.11", "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.6", + "@mui/utils": "^5.15.11", "clsx": "^2.1.0", - "csstype": "^3.1.2", + "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { @@ -4039,11 +4039,11 @@ } }, "node_modules/@mui/utils": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.6.tgz", - "integrity": "sha512-qfEhf+zfU9aQdbzo1qrSWlbPQhH1nCgeYgwhOVnj9Bn39shJQitEnXpSQpSNag8+uty5Od6PxmlNKPTnPySRKA==", + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.11.tgz", + "integrity": "sha512-D6bwqprUa9Stf8ft0dcMqWyWDKEo7D+6pB1k8WajbqlYIRA8J8Kw9Ra7PSZKKePGBGWO+/xxrX1U8HpG/aXQCw==", "dependencies": { - "@babel/runtime": "^7.23.8", + "@babel/runtime": "^7.23.9", "@types/prop-types": "^15.7.11", "prop-types": "^15.8.1", "react-is": "^18.2.0" @@ -4071,9 +4071,9 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/@mui/x-data-grid": { - "version": "6.18.7", - "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.18.7.tgz", - "integrity": "sha512-K1A3pMUPxI4/Mt5A4vrK45fBBQK5rZvBVqRMrB5n8zX++Bj+WLWKvLTtfCmlriUtzuadr/Hl7Z+FDRXUJAx6qg==", + "version": "6.19.5", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.19.5.tgz", + "integrity": "sha512-jV1ZqwyFslKqFScSn4t+xc/tNxLHOeJjz3HoeK+Wdf5t3bPM69pg/jLeg8TmOkAUY62JmQKCLVmcGWiR3AqUKQ==", "dependencies": { "@babel/runtime": "^7.23.2", "@mui/utils": "^5.14.16", @@ -7614,9 +7614,9 @@ } }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/currently-unhandled": { "version": "0.4.1", @@ -24359,9 +24359,9 @@ "dev": true }, "@babel/runtime": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", - "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", + "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -25142,28 +25142,28 @@ "dev": true }, "@floating-ui/core": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.3.tgz", - "integrity": "sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", "requires": { - "@floating-ui/utils": "^0.2.0" + "@floating-ui/utils": "^0.2.1" } }, "@floating-ui/dom": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.4.tgz", - "integrity": "sha512-jByEsHIY+eEdCjnTVu+E3ephzTOzkQ8hgUfGwos+bg7NlH33Zc5uO+QHz1mrQUOgIKKDD1RtS201P9NvAfq3XQ==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", "requires": { - "@floating-ui/core": "^1.5.3", + "@floating-ui/core": "^1.0.0", "@floating-ui/utils": "^0.2.0" } }, "@floating-ui/react-dom": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.6.tgz", - "integrity": "sha512-IB8aCRFxr8nFkdYZgH+Otd9EVQPJoynxeFRGTB8voPoZMRWo8XjYuCRgpI1btvuKY69XMiLnW+ym7zoBHM90Rw==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", "requires": { - "@floating-ui/dom": "^1.5.4" + "@floating-ui/dom": "^1.6.1" } }, "@floating-ui/utils": { @@ -25363,46 +25363,46 @@ } }, "@mui/base": { - "version": "5.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.32.tgz", - "integrity": "sha512-4VptvYeLUYMJhZapWBkD50GmKfOc0XT381KJcTK3ncZYIl8MdBhpR6l8jOyeP5cixUPBJhstjrnmQEAHjCLriw==", + "version": "5.0.0-beta.37", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.37.tgz", + "integrity": "sha512-/o3anbb+DeCng8jNsd3704XtmmLDZju1Fo8R2o7ugrVtPQ/QpcqddwKNzKPZwa0J5T8YNW3ZVuHyQgbTnQLisQ==", "requires": { - "@babel/runtime": "^7.23.8", - "@floating-ui/react-dom": "^2.0.5", + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.5", + "@mui/utils": "^5.15.11", "@popperjs/core": "^2.11.8", "clsx": "^2.1.0", "prop-types": "^15.8.1" } }, "@mui/core-downloads-tracker": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.6.tgz", - "integrity": "sha512-0aoWS4qvk1uzm9JBs83oQmIMIQeTBUeqqu8u+3uo2tMznrB5fIKqQVCbCgq+4Tm4jG+5F7dIvnjvQ2aV7UKtdw==" + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.11.tgz", + "integrity": "sha512-JVrJ9Jo4gyU707ujnRzmE8ABBWpXd6FwL9GYULmwZRtfPg89ggXs/S3MStQkpJ1JRWfdLL6S5syXmgQGq5EDAw==" }, "@mui/icons-material": { - "version": "5.15.5", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.5.tgz", - "integrity": "sha512-qiql0fd1JY7TZ1wm1RldvU7sL8QUatE9OC12i/qm5rnm/caTFyAfOyTIR7qqxorsJvoZGyrzwoMkal6Ij9kM0A==", + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.11.tgz", + "integrity": "sha512-R5ZoQqnKpd+5Ew7mBygTFLxgYsQHPhgR3TDXSgIHYIjGzYuyPLmGLSdcPUoMdi6kxiYqHlpPj4NJxlbaFD0UHA==", "requires": { - "@babel/runtime": "^7.23.8" + "@babel/runtime": "^7.23.9" } }, "@mui/material": { - "version": "5.15.5", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.5.tgz", - "integrity": "sha512-2KfA39f/UWeQl0O22UJs3x1nG3chYlyu9wnux5vTnxUTLzkgYIzQIHaH+ZOGpv5JiZBMKktAPNfhqyhSaQ49qQ==", + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.11.tgz", + "integrity": "sha512-FA3eEuEZaDaxgN3CgfXezMWbCZ4VCeU/sv0F0/PK5n42qIgsPVD6q+j71qS7/62sp6wRFMHtDMpXRlN+tT/7NA==", "requires": { - "@babel/runtime": "^7.23.8", - "@mui/base": "5.0.0-beta.32", - "@mui/core-downloads-tracker": "^5.15.5", - "@mui/system": "^5.15.5", + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.37", + "@mui/core-downloads-tracker": "^5.15.11", + "@mui/system": "^5.15.11", "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.5", + "@mui/utils": "^5.15.11", "@types/react-transition-group": "^4.4.10", "clsx": "^2.1.0", - "csstype": "^3.1.2", + "csstype": "^3.1.3", "prop-types": "^15.8.1", "react-is": "^18.2.0", "react-transition-group": "^4.4.5" @@ -25416,38 +25416,38 @@ } }, "@mui/private-theming": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.6.tgz", - "integrity": "sha512-ZBX9E6VNUSscUOtU8uU462VvpvBS7eFl5VfxAzTRVQBHflzL+5KtnGrebgf6Nd6cdvxa1o0OomiaxSKoN2XDmg==", + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.11.tgz", + "integrity": "sha512-jY/696SnSxSzO1u86Thym7ky5T9CgfidU3NFJjguldqK4f3Z5S97amZ6nffg8gTD0HBjY9scB+4ekqDEUmxZOA==", "requires": { - "@babel/runtime": "^7.23.8", - "@mui/utils": "^5.15.6", + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.15.11", "prop-types": "^15.8.1" } }, "@mui/styled-engine": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.6.tgz", - "integrity": "sha512-KAn8P8xP/WigFKMlEYUpU9z2o7jJnv0BG28Qu1dhNQVutsLVIFdRf5Nb+0ijp2qgtcmygQ0FtfRuXv5LYetZTg==", + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.11.tgz", + "integrity": "sha512-So21AhAngqo07ces4S/JpX5UaMU2RHXpEA6hNzI6IQjd/1usMPxpgK8wkGgTe3JKmC2KDmH8cvoycq5H3Ii7/w==", "requires": { - "@babel/runtime": "^7.23.8", + "@babel/runtime": "^7.23.9", "@emotion/cache": "^11.11.0", - "csstype": "^3.1.2", + "csstype": "^3.1.3", "prop-types": "^15.8.1" } }, "@mui/system": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.6.tgz", - "integrity": "sha512-J01D//u8IfXvaEHMBQX5aO2l7Q+P15nt96c4NskX7yp5/+UuZP8XCQJhtBtLuj+M2LLyXHYGmCPeblsmmscP2Q==", + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.11.tgz", + "integrity": "sha512-9j35suLFq+MgJo5ktVSHPbkjDLRMBCV17NMBdEQurh6oWyGnLM4uhU4QGZZQ75o0vuhjJghOCA1jkO3+79wKsA==", "requires": { - "@babel/runtime": "^7.23.8", - "@mui/private-theming": "^5.15.6", - "@mui/styled-engine": "^5.15.6", + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.15.11", + "@mui/styled-engine": "^5.15.11", "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.6", + "@mui/utils": "^5.15.11", "clsx": "^2.1.0", - "csstype": "^3.1.2", + "csstype": "^3.1.3", "prop-types": "^15.8.1" } }, @@ -25458,11 +25458,11 @@ "requires": {} }, "@mui/utils": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.6.tgz", - "integrity": "sha512-qfEhf+zfU9aQdbzo1qrSWlbPQhH1nCgeYgwhOVnj9Bn39shJQitEnXpSQpSNag8+uty5Od6PxmlNKPTnPySRKA==", + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.11.tgz", + "integrity": "sha512-D6bwqprUa9Stf8ft0dcMqWyWDKEo7D+6pB1k8WajbqlYIRA8J8Kw9Ra7PSZKKePGBGWO+/xxrX1U8HpG/aXQCw==", "requires": { - "@babel/runtime": "^7.23.8", + "@babel/runtime": "^7.23.9", "@types/prop-types": "^15.7.11", "prop-types": "^15.8.1", "react-is": "^18.2.0" @@ -25476,9 +25476,9 @@ } }, "@mui/x-data-grid": { - "version": "6.18.7", - "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.18.7.tgz", - "integrity": "sha512-K1A3pMUPxI4/Mt5A4vrK45fBBQK5rZvBVqRMrB5n8zX++Bj+WLWKvLTtfCmlriUtzuadr/Hl7Z+FDRXUJAx6qg==", + "version": "6.19.5", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.19.5.tgz", + "integrity": "sha512-jV1ZqwyFslKqFScSn4t+xc/tNxLHOeJjz3HoeK+Wdf5t3bPM69pg/jLeg8TmOkAUY62JmQKCLVmcGWiR3AqUKQ==", "requires": { "@babel/runtime": "^7.23.2", "@mui/utils": "^5.14.16", @@ -28078,9 +28078,9 @@ } }, "csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "currently-unhandled": { "version": "0.4.1", diff --git a/package.json b/package.json index bec653acfd..793f3d80f5 100644 --- a/package.json +++ b/package.json @@ -80,9 +80,9 @@ "@fontsource/noto-sans-tc": "5.0.17", "@jellyfin/sdk": "0.0.0-unstable.202403040506", "@loadable/component": "5.16.3", - "@mui/icons-material": "5.15.5", - "@mui/material": "5.15.5", - "@mui/x-data-grid": "6.18.7", + "@mui/icons-material": "5.15.11", + "@mui/material": "5.15.11", + "@mui/x-data-grid": "6.19.5", "@react-hook/resize-observer": "1.2.6", "@tanstack/react-query": "4.36.1", "@tanstack/react-query-devtools": "4.36.1", From 7f42b4e8b6be40f6dcf96a058793a836e49bc1ee Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Mon, 4 Mar 2024 13:46:12 -0500 Subject: [PATCH 55/90] Remove dlna configuration pages --- .../drawer/sections/DevicesDrawerSection.tsx | 26 +- src/apps/dashboard/routes/_asyncRoutes.ts | 1 + src/apps/dashboard/routes/_legacyRoutes.ts | 18 - src/apps/dashboard/routes/_redirects.ts | 4 +- src/apps/dashboard/routes/dlna.tsx | 33 + src/controllers/dashboard/dlna/profile.html | 584 ------------ src/controllers/dashboard/dlna/profile.js | 830 ------------------ src/controllers/dashboard/dlna/profiles.html | 32 - src/controllers/dashboard/dlna/profiles.js | 93 -- src/controllers/dashboard/dlna/settings.html | 69 -- src/controllers/dashboard/dlna/settings.js | 60 -- src/strings/en-us.json | 120 +-- 12 files changed, 39 insertions(+), 1831 deletions(-) create mode 100644 src/apps/dashboard/routes/dlna.tsx delete mode 100644 src/controllers/dashboard/dlna/profile.html delete mode 100644 src/controllers/dashboard/dlna/profile.js delete mode 100644 src/controllers/dashboard/dlna/profiles.html delete mode 100644 src/controllers/dashboard/dlna/profiles.js delete mode 100644 src/controllers/dashboard/dlna/settings.html delete mode 100644 src/controllers/dashboard/dlna/settings.js diff --git a/src/apps/dashboard/components/drawer/sections/DevicesDrawerSection.tsx b/src/apps/dashboard/components/drawer/sections/DevicesDrawerSection.tsx index 6cc7ab79fc..6d788f6b40 100644 --- a/src/apps/dashboard/components/drawer/sections/DevicesDrawerSection.tsx +++ b/src/apps/dashboard/components/drawer/sections/DevicesDrawerSection.tsx @@ -1,26 +1,15 @@ -import { Devices, Analytics, Input, ExpandLess, ExpandMore } from '@mui/icons-material'; -import Collapse from '@mui/material/Collapse'; +import { Devices, Analytics, Input } from '@mui/icons-material'; import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import ListSubheader from '@mui/material/ListSubheader'; import React from 'react'; -import { useLocation } from 'react-router-dom'; import ListItemLink from 'components/ListItemLink'; import globalize from 'scripts/globalize'; -const DLNA_PATHS = [ - '/dashboard/dlna', - '/dashboard/dlna/profiles' -]; - const DevicesDrawerSection = () => { - const location = useLocation(); - - const isDlnaSectionOpen = DLNA_PATHS.includes(location.pathname); - return ( { - + - {isDlnaSectionOpen ? : } - - - - - - - - - - ); }; diff --git a/src/apps/dashboard/routes/_asyncRoutes.ts b/src/apps/dashboard/routes/_asyncRoutes.ts index 9abd6b75e3..0e33b2a0d4 100644 --- a/src/apps/dashboard/routes/_asyncRoutes.ts +++ b/src/apps/dashboard/routes/_asyncRoutes.ts @@ -2,6 +2,7 @@ import { AsyncRouteType, type AsyncRoute } from 'components/router/AsyncRoute'; export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ { path: 'activity', type: AsyncRouteType.Dashboard }, + { path: 'dlna', type: AsyncRouteType.Dashboard }, { path: 'notifications', type: AsyncRouteType.Dashboard }, { path: 'users', type: AsyncRouteType.Dashboard }, { path: 'users/access', type: AsyncRouteType.Dashboard }, diff --git a/src/apps/dashboard/routes/_legacyRoutes.ts b/src/apps/dashboard/routes/_legacyRoutes.ts index efdd543a42..6adf825dc3 100644 --- a/src/apps/dashboard/routes/_legacyRoutes.ts +++ b/src/apps/dashboard/routes/_legacyRoutes.ts @@ -31,24 +31,6 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [ controller: 'dashboard/devices/device', view: 'dashboard/devices/device.html' } - }, { - path: 'dlna/profiles/edit', - pageProps: { - controller: 'dashboard/dlna/profile', - view: 'dashboard/dlna/profile.html' - } - }, { - path: 'dlna/profiles', - pageProps: { - controller: 'dashboard/dlna/profiles', - view: 'dashboard/dlna/profiles.html' - } - }, { - path: 'dlna', - pageProps: { - controller: 'dashboard/dlna/settings', - view: 'dashboard/dlna/settings.html' - } }, { path: 'plugins/add', pageProps: { diff --git a/src/apps/dashboard/routes/_redirects.ts b/src/apps/dashboard/routes/_redirects.ts index 94211c79c2..b7fdf07218 100644 --- a/src/apps/dashboard/routes/_redirects.ts +++ b/src/apps/dashboard/routes/_redirects.ts @@ -8,8 +8,8 @@ export const REDIRECTS: Redirect[] = [ { from: 'dashboardgeneral.html', to: '/dashboard/settings' }, { from: 'device.html', to: '/dashboard/devices/edit' }, { from: 'devices.html', to: '/dashboard/devices' }, - { from: 'dlnaprofile.html', to: '/dashboard/dlna/profiles/edit' }, - { from: 'dlnaprofiles.html', to: '/dashboard/dlna/profiles' }, + { from: 'dlnaprofile.html', to: '/dashboard/dlna' }, + { from: 'dlnaprofiles.html', to: '/dashboard/dlna' }, { from: 'dlnasettings.html', to: '/dashboard/dlna' }, { from: 'edititemmetadata.html', to: '/metadata' }, { from: 'encodingsettings.html', to: '/dashboard/playback/transcoding' }, diff --git a/src/apps/dashboard/routes/dlna.tsx b/src/apps/dashboard/routes/dlna.tsx new file mode 100644 index 0000000000..fec7d2fdb0 --- /dev/null +++ b/src/apps/dashboard/routes/dlna.tsx @@ -0,0 +1,33 @@ +import Alert from '@mui/material/Alert/Alert'; +import Box from '@mui/material/Box/Box'; +import Button from '@mui/material/Button/Button'; +import React from 'react'; +import { Link } from 'react-router-dom'; + +import Page from 'components/Page'; +import globalize from 'scripts/globalize'; + +const DlnaPage = () => ( + +
+

DLNA

+ + + {globalize.translate('DlnaMovedMessage')} + + + +
+
+); + +export default DlnaPage; diff --git a/src/controllers/dashboard/dlna/profile.html b/src/controllers/dashboard/dlna/profile.html deleted file mode 100644 index 237874e156..0000000000 --- a/src/controllers/dashboard/dlna/profile.html +++ /dev/null @@ -1,584 +0,0 @@ -
-
-
-
-
-
-

${HeaderProfileInformation}

-
-
- -
-
-
- -
-
- -
${LabelUserLibraryHelp}
-
-
-

${LabelSupportedMediaTypes}

-
- - - -
-
-
-
- -
${LabelMaxStreamingBitrateHelp}
-
-
- -
${LabelMusicStreamingTranscodingBitrateHelp}
-
-
- - -
${OptionIgnoreTranscodeByteRangeRequestsHelp}
-
-
-
-

${HeaderIdentificationCriteriaHelp}

-
- -
${LabelIdentificationFieldHelp}
-
-
- -
${LabelIdentificationFieldHelp}
-
-
- -
${LabelIdentificationFieldHelp}
-
-
- -
${LabelIdentificationFieldHelp}
-
-
- -
${LabelIdentificationFieldHelp}
-
-
- -
${LabelIdentificationFieldHelp}
-
-
- -
${LabelIdentificationFieldHelp}
-
-
- -
${LabelIdentificationFieldHelp}
-
-
- -
${LabelIdentificationFieldHelp}
-
-
-

${HeaderHttpHeaders}

- -
-
-
-
-
-
-
-
- -
${OptionPlainStorageFoldersHelp}
-
-
- -
${OptionPlainVideoItemsHelp}
-
-
-
-
-
-
- -
${LabelEmbedAlbumArtDidlHelp}
-
-
- -
${LabelEnableSingleImageInDidlLimitHelp}
-
-
- -
${LabelAlbumArtHelp}
-
-
- -
${LabelAlbumArtMaxResHelp}
-
-
- -
${LabelAlbumArtMaxResHelp}
-
-
- -
${LabelIconMaxResHelp}
-
-
- -
${LabelIconMaxResHelp}
-
-
-
-
-
-

${HeaderProfileServerSettingsHelp}

-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
${LabelProtocolInfoHelp}
-
-
- -
${LabelXDlnaCapHelp}
-
-
- -
${LabelXDlnaDocHelp}
-
-
- -
${LabelSonyAggregationFlagsHelp}
-
-
-
-
-
-

${HeaderSubtitleProfilesHelp}

- -
-
-
-
-
-
-
-

${HeaderXmlDocumentAttributes}

- -
-
-
${XmlDocumentAttributeListHelp}
-
-
-
-
-
-

${HeaderDirectPlayProfileHelp}

- -
-
-
-
-

${HeaderTranscodingProfileHelp}

- -
-
-
-
-

${HeaderContainerProfileHelp}

- -
-
-
-
-

${HeaderCodecProfileHelp}

- -
-
-
-
-

${HeaderResponseProfileHelp}

- -
-
-
-
-
- - -
-
-
-
-
-
-
-

${HeaderDirectPlayProfile}

-
-
-
- -
-
- -
${LabelProfileContainersHelp}
-
-
-
- -
${LabelProfileCodecsHelp}
-
-
-
-
- -
${LabelProfileCodecsHelp}
-
-
-

- - -

-
-
-
-
-
-
-

${HeaderTranscodingProfile}

-
-
-
- - - - -
-
- - -

- - -

-
-
-
-
-
-
-

${HeaderContainerProfile}

-
-
-

${HeaderContainerProfileHelp}

-
-
- -
-
- -
${LabelProfileContainersHelp}
-
-
- -

- - -

-
-
-
-
-
-
-

${HeaderCodecProfile}

-
-
-

${HeaderCodecProfileHelp}

-
- -
-
- -
${LabelProfileCodecsHelp}
-
-

- - -

-
-
-
-
-
-
-

${HeaderResponseProfile}

-
-
-
- -
-
- -
${LabelProfileContainersHelp}
-
-
-
- -
${LabelProfileCodecsHelp}
-
-
-
-
- -
${LabelProfileCodecsHelp}
-
-
-

- - -

-
-
-
-
-
-
-

${HeaderIdentificationHeader}

-
-
-
- -
-
- -
-
- -
-

- - -

-
-
-
-
-
-
-

${HeaderXmlDocumentAttribute}

-
-
-
- -
-
- -
-

- - -

-
-
-
-
-
-
-

${HeaderSubtitleProfile}

-
-
-
- -
${LabelSubtitleFormatHelp}
-
-
- -
-
- -
-

- - -

-
-
-
-
diff --git a/src/controllers/dashboard/dlna/profile.js b/src/controllers/dashboard/dlna/profile.js deleted file mode 100644 index 0f92a3200c..0000000000 --- a/src/controllers/dashboard/dlna/profile.js +++ /dev/null @@ -1,830 +0,0 @@ -import escapeHtml from 'escape-html'; -import 'jquery'; -import loading from '../../../components/loading/loading'; -import globalize from '../../../scripts/globalize'; -import '../../../elements/emby-select/emby-select'; -import '../../../elements/emby-button/emby-button'; -import '../../../elements/emby-input/emby-input'; -import '../../../elements/emby-checkbox/emby-checkbox'; -import '../../../components/listview/listview.scss'; -import Dashboard from '../../../utils/dashboard'; -import toast from '../../../components/toast/toast'; -import { getParameterByName } from '../../../utils/url.ts'; - -function loadProfile(page) { - loading.show(); - const promise1 = getProfile(); - const promise2 = ApiClient.getUsers(); - Promise.all([promise1, promise2]).then(function (responses) { - currentProfile = responses[0]; - renderProfile(page, currentProfile, responses[1]); - loading.hide(); - }); -} - -function getProfile() { - const id = getParameterByName('id'); - const url = id ? 'Dlna/Profiles/' + id : 'Dlna/Profiles/Default'; - return ApiClient.getJSON(ApiClient.getUrl(url)); -} - -function renderProfile(page, profile, users) { - $('#txtName', page).val(profile.Name); - $('.chkMediaType', page).each(function () { - this.checked = (profile.SupportedMediaTypes || '').split(',').indexOf(this.getAttribute('data-value')) != -1; - }); - $('#chkEnableAlbumArtInDidl', page).prop('checked', profile.EnableAlbumArtInDidl); - $('#chkEnableSingleImageLimit', page).prop('checked', profile.EnableSingleAlbumArtLimit); - renderXmlDocumentAttributes(page, profile.XmlRootAttributes || []); - const idInfo = profile.Identification || {}; - renderIdentificationHeaders(page, idInfo.Headers || []); - renderSubtitleProfiles(page, profile.SubtitleProfiles || []); - $('#txtInfoFriendlyName', page).val(profile.FriendlyName || ''); - $('#txtInfoModelName', page).val(profile.ModelName || ''); - $('#txtInfoModelNumber', page).val(profile.ModelNumber || ''); - $('#txtInfoModelDescription', page).val(profile.ModelDescription || ''); - $('#txtInfoModelUrl', page).val(profile.ModelUrl || ''); - $('#txtInfoManufacturer', page).val(profile.Manufacturer || ''); - $('#txtInfoManufacturerUrl', page).val(profile.ManufacturerUrl || ''); - $('#txtInfoSerialNumber', page).val(profile.SerialNumber || ''); - $('#txtIdFriendlyName', page).val(idInfo.FriendlyName || ''); - $('#txtIdModelName', page).val(idInfo.ModelName || ''); - $('#txtIdModelNumber', page).val(idInfo.ModelNumber || ''); - $('#txtIdModelDescription', page).val(idInfo.ModelDescription || ''); - $('#txtIdModelUrl', page).val(idInfo.ModelUrl || ''); - $('#txtIdManufacturer', page).val(idInfo.Manufacturer || ''); - $('#txtIdManufacturerUrl', page).val(idInfo.ManufacturerUrl || ''); - $('#txtIdSerialNumber', page).val(idInfo.SerialNumber || ''); - $('#txtIdDeviceDescription', page).val(idInfo.DeviceDescription || ''); - $('#txtAlbumArtPn', page).val(profile.AlbumArtPn || ''); - $('#txtAlbumArtMaxWidth', page).val(profile.MaxAlbumArtWidth || ''); - $('#txtAlbumArtMaxHeight', page).val(profile.MaxAlbumArtHeight || ''); - $('#txtIconMaxWidth', page).val(profile.MaxIconWidth || ''); - $('#txtIconMaxHeight', page).val(profile.MaxIconHeight || ''); - $('#chkIgnoreTranscodeByteRangeRequests', page).prop('checked', profile.IgnoreTranscodeByteRangeRequests); - $('#txtMaxAllowedBitrate', page).val(profile.MaxStreamingBitrate || ''); - $('#txtMusicStreamingTranscodingBitrate', page).val(profile.MusicStreamingTranscodingBitrate || ''); - $('#chkRequiresPlainFolders', page).prop('checked', profile.RequiresPlainFolders); - $('#chkRequiresPlainVideoItems', page).prop('checked', profile.RequiresPlainVideoItems); - $('#txtProtocolInfo', page).val(profile.ProtocolInfo || ''); - $('#txtXDlnaCap', page).val(profile.XDlnaCap || ''); - $('#txtXDlnaDoc', page).val(profile.XDlnaDoc || ''); - $('#txtSonyAggregationFlags', page).val(profile.SonyAggregationFlags || ''); - profile.DirectPlayProfiles = profile.DirectPlayProfiles || []; - profile.TranscodingProfiles = profile.TranscodingProfiles || []; - profile.ContainerProfiles = profile.ContainerProfiles || []; - profile.CodecProfiles = profile.CodecProfiles || []; - profile.ResponseProfiles = profile.ResponseProfiles || []; - const usersHtml = '' + users.map(function (u) { - return ''; - }).join(''); - $('#selectUser', page).html(usersHtml).val(profile.UserId || ''); - renderSubProfiles(page, profile); -} - -function renderIdentificationHeaders(page, headers) { - let index = 0; - const html = '
' + headers.map(function (h) { - let li = '
'; - li += ''; - li += '
'; - li += '

' + escapeHtml(h.Name + ': ' + (h.Value || '')) + '

'; - li += '
' + escapeHtml(h.Match || '') + '
'; - li += '
'; - li += ''; - li += '
'; - index++; - return li; - }).join('') + '
'; - const elem = $('.httpHeaderIdentificationList', page).html(html).trigger('create'); - $('.btnDeleteIdentificationHeader', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index'), 10); - currentProfile.Identification.Headers.splice(itemIndex, 1); - renderIdentificationHeaders(page, currentProfile.Identification.Headers); - }); -} - -function openPopup(elem) { - elem.classList.remove('hide'); -} - -function closePopup(elem) { - elem.classList.add('hide'); -} - -function editIdentificationHeader(page, header) { - isSubProfileNew = header == null; - header = header || {}; - currentSubProfile = header; - const popup = $('#identificationHeaderPopup', page); - $('#txtIdentificationHeaderName', popup).val(header.Name || ''); - $('#txtIdentificationHeaderValue', popup).val(header.Value || ''); - $('#selectMatchType', popup).val(header.Match || 'Equals'); - openPopup(popup[0]); -} - -function saveIdentificationHeader(page) { - currentSubProfile.Name = $('#txtIdentificationHeaderName', page).val(); - currentSubProfile.Value = $('#txtIdentificationHeaderValue', page).val(); - currentSubProfile.Match = $('#selectMatchType', page).val(); - - if (isSubProfileNew) { - currentProfile.Identification = currentProfile.Identification || {}; - currentProfile.Identification.Headers = currentProfile.Identification.Headers || []; - currentProfile.Identification.Headers.push(currentSubProfile); - } - - renderIdentificationHeaders(page, currentProfile.Identification.Headers); - currentSubProfile = null; - closePopup($('#identificationHeaderPopup', page)[0]); -} - -function renderXmlDocumentAttributes(page, attribute) { - const html = '
' + attribute.map(function (h) { - let li = '
'; - li += ''; - li += '
'; - li += '

' + escapeHtml(h.Name + ' = ' + (h.Value || '')) + '

'; - li += '
'; - li += ''; - li += '
'; - return li; - }).join('') + '
'; - const elem = $('.xmlDocumentAttributeList', page).html(html).trigger('create'); - $('.btnDeleteXmlAttribute', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index'), 10); - currentProfile.XmlRootAttributes.splice(itemIndex, 1); - renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes); - }); -} - -function editXmlDocumentAttribute(page, attribute) { - isSubProfileNew = attribute == null; - attribute = attribute || {}; - currentSubProfile = attribute; - const popup = $('#xmlAttributePopup', page); - $('#txtXmlAttributeName', popup).val(attribute.Name || ''); - $('#txtXmlAttributeValue', popup).val(attribute.Value || ''); - openPopup(popup[0]); -} - -function saveXmlDocumentAttribute(page) { - currentSubProfile.Name = $('#txtXmlAttributeName', page).val(); - currentSubProfile.Value = $('#txtXmlAttributeValue', page).val(); - - if (isSubProfileNew) { - currentProfile.XmlRootAttributes.push(currentSubProfile); - } - - renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes); - currentSubProfile = null; - closePopup($('#xmlAttributePopup', page)[0]); -} - -function renderSubtitleProfiles(page, profiles) { - let index = 0; - const html = '
' + profiles.map(function (h) { - let li = '
'; - li += ''; - li += '
'; - li += '

' + escapeHtml(h.Format || '') + '

'; - li += '
'; - li += ''; - li += '
'; - index++; - return li; - }).join('') + '
'; - const elem = $('.subtitleProfileList', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index'), 10); - currentProfile.SubtitleProfiles.splice(itemIndex, 1); - renderSubtitleProfiles(page, currentProfile.SubtitleProfiles); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index'), 10); - editSubtitleProfile(page, currentProfile.SubtitleProfiles[itemIndex]); - }); -} - -function editSubtitleProfile(page, profile) { - isSubProfileNew = profile == null; - profile = profile || {}; - currentSubProfile = profile; - const popup = $('#subtitleProfilePopup', page); - $('#txtSubtitleProfileFormat', popup).val(profile.Format || ''); - $('#selectSubtitleProfileMethod', popup).val(profile.Method || ''); - $('#selectSubtitleProfileDidlMode', popup).val(profile.DidlMode || ''); - openPopup(popup[0]); -} - -function saveSubtitleProfile(page) { - currentSubProfile.Format = $('#txtSubtitleProfileFormat', page).val(); - currentSubProfile.Method = $('#selectSubtitleProfileMethod', page).val(); - currentSubProfile.DidlMode = $('#selectSubtitleProfileDidlMode', page).val(); - - if (isSubProfileNew) { - currentProfile.SubtitleProfiles.push(currentSubProfile); - } - - renderSubtitleProfiles(page, currentProfile.SubtitleProfiles); - currentSubProfile = null; - closePopup($('#subtitleProfilePopup', page)[0]); -} - -function renderSubProfiles(page, profile) { - renderDirectPlayProfiles(page, profile.DirectPlayProfiles); - renderTranscodingProfiles(page, profile.TranscodingProfiles); - renderContainerProfiles(page, profile.ContainerProfiles); - renderCodecProfiles(page, profile.CodecProfiles); - renderResponseProfiles(page, profile.ResponseProfiles); -} - -function saveDirectPlayProfile(page) { - currentSubProfile.Type = $('#selectDirectPlayProfileType', page).val(); - currentSubProfile.Container = $('#txtDirectPlayContainer', page).val(); - currentSubProfile.AudioCodec = $('#txtDirectPlayAudioCodec', page).val(); - currentSubProfile.VideoCodec = $('#txtDirectPlayVideoCodec', page).val(); - - if (isSubProfileNew) { - currentProfile.DirectPlayProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#popupEditDirectPlayProfile', page)[0]); -} - -function renderDirectPlayProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.directPlayProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteDirectPlayProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editDirectPlayProfile(page, currentProfile.DirectPlayProfiles[index]); - }); -} - -function deleteDirectPlayProfile(page, index) { - currentProfile.DirectPlayProfiles.splice(index, 1); - renderDirectPlayProfiles(page, currentProfile.DirectPlayProfiles); -} - -function editDirectPlayProfile(page, directPlayProfile) { - isSubProfileNew = directPlayProfile == null; - directPlayProfile = directPlayProfile || {}; - currentSubProfile = directPlayProfile; - const popup = $('#popupEditDirectPlayProfile', page); - $('#selectDirectPlayProfileType', popup).val(directPlayProfile.Type || 'Video').trigger('change'); - $('#txtDirectPlayContainer', popup).val(directPlayProfile.Container || ''); - $('#txtDirectPlayAudioCodec', popup).val(directPlayProfile.AudioCodec || ''); - $('#txtDirectPlayVideoCodec', popup).val(directPlayProfile.VideoCodec || ''); - openPopup(popup[0]); -} - -function renderTranscodingProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.transcodingProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteTranscodingProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editTranscodingProfile(page, currentProfile.TranscodingProfiles[index]); - }); -} - -function editTranscodingProfile(page, transcodingProfile) { - isSubProfileNew = transcodingProfile == null; - transcodingProfile = transcodingProfile || {}; - currentSubProfile = transcodingProfile; - const popup = $('#transcodingProfilePopup', page); - $('#selectTranscodingProfileType', popup).val(transcodingProfile.Type || 'Video').trigger('change'); - $('#txtTranscodingContainer', popup).val(transcodingProfile.Container || ''); - $('#txtTranscodingAudioCodec', popup).val(transcodingProfile.AudioCodec || ''); - $('#txtTranscodingVideoCodec', popup).val(transcodingProfile.VideoCodec || ''); - $('#selectTranscodingProtocol', popup).val(transcodingProfile.Protocol || 'Http'); - $('#chkEnableMpegtsM2TsMode', popup).prop('checked', transcodingProfile.EnableMpegtsM2TsMode || false); - $('#chkEstimateContentLength', popup).prop('checked', transcodingProfile.EstimateContentLength || false); - $('#chkReportByteRangeRequests', popup).prop('checked', transcodingProfile.TranscodeSeekInfo == 'Bytes'); - $('.radioTabButton:first', popup).trigger('click'); - openPopup(popup[0]); -} - -function deleteTranscodingProfile(page, index) { - currentProfile.TranscodingProfiles.splice(index, 1); - renderTranscodingProfiles(page, currentProfile.TranscodingProfiles); -} - -function saveTranscodingProfile(page) { - currentSubProfile.Type = $('#selectTranscodingProfileType', page).val(); - currentSubProfile.Container = $('#txtTranscodingContainer', page).val(); - currentSubProfile.AudioCodec = $('#txtTranscodingAudioCodec', page).val(); - currentSubProfile.VideoCodec = $('#txtTranscodingVideoCodec', page).val(); - currentSubProfile.Protocol = $('#selectTranscodingProtocol', page).val(); - currentSubProfile.Context = 'Streaming'; - currentSubProfile.EnableMpegtsM2TsMode = $('#chkEnableMpegtsM2TsMode', page).is(':checked'); - currentSubProfile.EstimateContentLength = $('#chkEstimateContentLength', page).is(':checked'); - currentSubProfile.TranscodeSeekInfo = $('#chkReportByteRangeRequests', page).is(':checked') ? 'Bytes' : 'Auto'; - - if (isSubProfileNew) { - currentProfile.TranscodingProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#transcodingProfilePopup', page)[0]); -} - -function renderContainerProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.containerProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteContainerProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editContainerProfile(page, currentProfile.ContainerProfiles[index]); - }); -} - -function deleteContainerProfile(page, index) { - currentProfile.ContainerProfiles.splice(index, 1); - renderContainerProfiles(page, currentProfile.ContainerProfiles); -} - -function editContainerProfile(page, containerProfile) { - isSubProfileNew = containerProfile == null; - containerProfile = containerProfile || {}; - currentSubProfile = containerProfile; - const popup = $('#containerProfilePopup', page); - $('#selectContainerProfileType', popup).val(containerProfile.Type || 'Video').trigger('change'); - $('#txtContainerProfileContainer', popup).val(containerProfile.Container || ''); - $('.radioTabButton:first', popup).trigger('click'); - openPopup(popup[0]); -} - -function saveContainerProfile(page) { - currentSubProfile.Type = $('#selectContainerProfileType', page).val(); - currentSubProfile.Container = $('#txtContainerProfileContainer', page).val(); - - if (isSubProfileNew) { - currentProfile.ContainerProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#containerProfilePopup', page)[0]); -} - -function renderCodecProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.codecProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteCodecProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editCodecProfile(page, currentProfile.CodecProfiles[index]); - }); -} - -function deleteCodecProfile(page, index) { - currentProfile.CodecProfiles.splice(index, 1); - renderCodecProfiles(page, currentProfile.CodecProfiles); -} - -function editCodecProfile(page, codecProfile) { - isSubProfileNew = codecProfile == null; - codecProfile = codecProfile || {}; - currentSubProfile = codecProfile; - const popup = $('#codecProfilePopup', page); - $('#selectCodecProfileType', popup).val(codecProfile.Type || 'Video').trigger('change'); - $('#txtCodecProfileCodec', popup).val(codecProfile.Codec || ''); - $('.radioTabButton:first', popup).trigger('click'); - openPopup(popup[0]); -} - -function saveCodecProfile(page) { - currentSubProfile.Type = $('#selectCodecProfileType', page).val(); - currentSubProfile.Codec = $('#txtCodecProfileCodec', page).val(); - - if (isSubProfileNew) { - currentProfile.CodecProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#codecProfilePopup', page)[0]); -} - -function renderResponseProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.mediaProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteResponseProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editResponseProfile(page, currentProfile.ResponseProfiles[index]); - }); -} - -function deleteResponseProfile(page, index) { - currentProfile.ResponseProfiles.splice(index, 1); - renderResponseProfiles(page, currentProfile.ResponseProfiles); -} - -function editResponseProfile(page, responseProfile) { - isSubProfileNew = responseProfile == null; - responseProfile = responseProfile || {}; - currentSubProfile = responseProfile; - const popup = $('#responseProfilePopup', page); - $('#selectResponseProfileType', popup).val(responseProfile.Type || 'Video').trigger('change'); - $('#txtResponseProfileContainer', popup).val(responseProfile.Container || ''); - $('#txtResponseProfileAudioCodec', popup).val(responseProfile.AudioCodec || ''); - $('#txtResponseProfileVideoCodec', popup).val(responseProfile.VideoCodec || ''); - $('.radioTabButton:first', popup).trigger('click'); - openPopup(popup[0]); -} - -function saveResponseProfile(page) { - currentSubProfile.Type = $('#selectResponseProfileType', page).val(); - currentSubProfile.Container = $('#txtResponseProfileContainer', page).val(); - currentSubProfile.AudioCodec = $('#txtResponseProfileAudioCodec', page).val(); - currentSubProfile.VideoCodec = $('#txtResponseProfileVideoCodec', page).val(); - - if (isSubProfileNew) { - currentProfile.ResponseProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#responseProfilePopup', page)[0]); -} - -function saveProfile(page, profile) { - updateProfile(page, profile); - const id = getParameterByName('id'); - - if (id) { - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('Dlna/Profiles/' + id), - data: JSON.stringify(profile), - contentType: 'application/json' - }).then(function () { - toast(globalize.translate('SettingsSaved')); - }, Dashboard.processErrorResponse); - } else { - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('Dlna/Profiles'), - data: JSON.stringify(profile), - contentType: 'application/json' - }).then(function () { - Dashboard.navigate('dashboard/dlna/profiles'); - }, Dashboard.processErrorResponse); - } - - loading.hide(); -} - -function updateProfile(page, profile) { - profile.Name = $('#txtName', page).val(); - profile.EnableAlbumArtInDidl = $('#chkEnableAlbumArtInDidl', page).is(':checked'); - profile.EnableSingleAlbumArtLimit = $('#chkEnableSingleImageLimit', page).is(':checked'); - profile.SupportedMediaTypes = $('.chkMediaType:checked', page).get().map(function (c) { - return c.getAttribute('data-value'); - }).join(','); - profile.Identification = profile.Identification || {}; - profile.FriendlyName = $('#txtInfoFriendlyName', page).val(); - profile.ModelName = $('#txtInfoModelName', page).val(); - profile.ModelNumber = $('#txtInfoModelNumber', page).val(); - profile.ModelDescription = $('#txtInfoModelDescription', page).val(); - profile.ModelUrl = $('#txtInfoModelUrl', page).val(); - profile.Manufacturer = $('#txtInfoManufacturer', page).val(); - profile.ManufacturerUrl = $('#txtInfoManufacturerUrl', page).val(); - profile.SerialNumber = $('#txtInfoSerialNumber', page).val(); - profile.Identification.FriendlyName = $('#txtIdFriendlyName', page).val(); - profile.Identification.ModelName = $('#txtIdModelName', page).val(); - profile.Identification.ModelNumber = $('#txtIdModelNumber', page).val(); - profile.Identification.ModelDescription = $('#txtIdModelDescription', page).val(); - profile.Identification.ModelUrl = $('#txtIdModelUrl', page).val(); - profile.Identification.Manufacturer = $('#txtIdManufacturer', page).val(); - profile.Identification.ManufacturerUrl = $('#txtIdManufacturerUrl', page).val(); - profile.Identification.SerialNumber = $('#txtIdSerialNumber', page).val(); - profile.Identification.DeviceDescription = $('#txtIdDeviceDescription', page).val(); - profile.AlbumArtPn = $('#txtAlbumArtPn', page).val(); - profile.MaxAlbumArtWidth = $('#txtAlbumArtMaxWidth', page).val(); - profile.MaxAlbumArtHeight = $('#txtAlbumArtMaxHeight', page).val(); - profile.MaxIconWidth = $('#txtIconMaxWidth', page).val(); - profile.MaxIconHeight = $('#txtIconMaxHeight', page).val(); - profile.RequiresPlainFolders = $('#chkRequiresPlainFolders', page).is(':checked'); - profile.RequiresPlainVideoItems = $('#chkRequiresPlainVideoItems', page).is(':checked'); - profile.IgnoreTranscodeByteRangeRequests = $('#chkIgnoreTranscodeByteRangeRequests', page).is(':checked'); - profile.MaxStreamingBitrate = $('#txtMaxAllowedBitrate', page).val(); - profile.MusicStreamingTranscodingBitrate = $('#txtMusicStreamingTranscodingBitrate', page).val(); - profile.ProtocolInfo = $('#txtProtocolInfo', page).val(); - profile.XDlnaCap = $('#txtXDlnaCap', page).val(); - profile.XDlnaDoc = $('#txtXDlnaDoc', page).val(); - profile.SonyAggregationFlags = $('#txtSonyAggregationFlags', page).val(); - profile.UserId = $('#selectUser', page).val(); -} - -let currentProfile; -let currentSubProfile; -let isSubProfileNew; -const allText = globalize.translate('All'); - -$(document).on('pageinit', '#dlnaProfilePage', function () { - const page = this; - $('.radioTabButton', page).on('click', function () { - $(this).siblings().removeClass('ui-btn-active'); - $(this).addClass('ui-btn-active'); - const value = this.tagName == 'A' ? this.getAttribute('data-value') : this.value; - const elem = $('.' + value, page); - elem.siblings('.tabContent').hide(); - elem.show(); - }); - $('#selectDirectPlayProfileType', page).on('change', function () { - if (this.value == 'Video') { - $('#fldDirectPlayVideoCodec', page).show(); - } else { - $('#fldDirectPlayVideoCodec', page).hide(); - } - - if (this.value == 'Photo') { - $('#fldDirectPlayAudioCodec', page).hide(); - } else { - $('#fldDirectPlayAudioCodec', page).show(); - } - }); - $('#selectTranscodingProfileType', page).on('change', function () { - if (this.value == 'Video') { - $('#fldTranscodingVideoCodec', page).show(); - $('#fldTranscodingProtocol', page).show(); - $('#fldEnableMpegtsM2TsMode', page).show(); - } else { - $('#fldTranscodingVideoCodec', page).hide(); - $('#fldTranscodingProtocol', page).hide(); - $('#fldEnableMpegtsM2TsMode', page).hide(); - } - - if (this.value == 'Photo') { - $('#fldTranscodingAudioCodec', page).hide(); - $('#fldEstimateContentLength', page).hide(); - $('#fldReportByteRangeRequests', page).hide(); - } else { - $('#fldTranscodingAudioCodec', page).show(); - $('#fldEstimateContentLength', page).show(); - $('#fldReportByteRangeRequests', page).show(); - } - }); - $('#selectResponseProfileType', page).on('change', function () { - if (this.value == 'Video') { - $('#fldResponseProfileVideoCodec', page).show(); - } else { - $('#fldResponseProfileVideoCodec', page).hide(); - } - - if (this.value == 'Photo') { - $('#fldResponseProfileAudioCodec', page).hide(); - } else { - $('#fldResponseProfileAudioCodec', page).show(); - } - }); - $('.btnAddDirectPlayProfile', page).on('click', function () { - editDirectPlayProfile(page); - }); - $('.btnAddTranscodingProfile', page).on('click', function () { - editTranscodingProfile(page); - }); - $('.btnAddContainerProfile', page).on('click', function () { - editContainerProfile(page); - }); - $('.btnAddCodecProfile', page).on('click', function () { - editCodecProfile(page); - }); - $('.btnAddResponseProfile', page).on('click', function () { - editResponseProfile(page); - }); - $('.btnAddIdentificationHttpHeader', page).on('click', function () { - editIdentificationHeader(page); - }); - $('.btnAddXmlDocumentAttribute', page).on('click', function () { - editXmlDocumentAttribute(page); - }); - $('.btnAddSubtitleProfile', page).on('click', function () { - editSubtitleProfile(page); - }); - $('.dlnaProfileForm').off('submit', DlnaProfilePage.onSubmit).on('submit', DlnaProfilePage.onSubmit); - $('.editDirectPlayProfileForm').off('submit', DlnaProfilePage.onDirectPlayFormSubmit).on('submit', DlnaProfilePage.onDirectPlayFormSubmit); - $('.transcodingProfileForm').off('submit', DlnaProfilePage.onTranscodingProfileFormSubmit).on('submit', DlnaProfilePage.onTranscodingProfileFormSubmit); - $('.containerProfileForm').off('submit', DlnaProfilePage.onContainerProfileFormSubmit).on('submit', DlnaProfilePage.onContainerProfileFormSubmit); - $('.codecProfileForm').off('submit', DlnaProfilePage.onCodecProfileFormSubmit).on('submit', DlnaProfilePage.onCodecProfileFormSubmit); - $('.editResponseProfileForm').off('submit', DlnaProfilePage.onResponseProfileFormSubmit).on('submit', DlnaProfilePage.onResponseProfileFormSubmit); - $('.identificationHeaderForm').off('submit', DlnaProfilePage.onIdentificationHeaderFormSubmit).on('submit', DlnaProfilePage.onIdentificationHeaderFormSubmit); - $('.xmlAttributeForm').off('submit', DlnaProfilePage.onXmlAttributeFormSubmit).on('submit', DlnaProfilePage.onXmlAttributeFormSubmit); - $('.subtitleProfileForm').off('submit', DlnaProfilePage.onSubtitleProfileFormSubmit).on('submit', DlnaProfilePage.onSubtitleProfileFormSubmit); -}).on('pageshow', '#dlnaProfilePage', function () { - const page = this; - $('#radioInfo', page).trigger('click'); - loadProfile(page); -}); -window.DlnaProfilePage = { - onSubmit: function () { - loading.show(); - saveProfile($(this).parents('.page'), currentProfile); - return false; - }, - onDirectPlayFormSubmit: function () { - saveDirectPlayProfile($(this).parents('.page')); - return false; - }, - onTranscodingProfileFormSubmit: function () { - saveTranscodingProfile($(this).parents('.page')); - return false; - }, - onContainerProfileFormSubmit: function () { - saveContainerProfile($(this).parents('.page')); - return false; - }, - onCodecProfileFormSubmit: function () { - saveCodecProfile($(this).parents('.page')); - return false; - }, - onResponseProfileFormSubmit: function () { - saveResponseProfile($(this).parents('.page')); - return false; - }, - onIdentificationHeaderFormSubmit: function () { - saveIdentificationHeader($(this).parents('.page')); - return false; - }, - onXmlAttributeFormSubmit: function () { - saveXmlDocumentAttribute($(this).parents('.page')); - return false; - }, - onSubtitleProfileFormSubmit: function () { - saveSubtitleProfile($(this).parents('.page')); - return false; - } -}; - diff --git a/src/controllers/dashboard/dlna/profiles.html b/src/controllers/dashboard/dlna/profiles.html deleted file mode 100644 index f1696632c9..0000000000 --- a/src/controllers/dashboard/dlna/profiles.html +++ /dev/null @@ -1,32 +0,0 @@ -
- -
-
- -
- -
-
-

${HeaderCustomDlnaProfiles}

- - - -
- -

${CustomDlnaProfilesHelp}

-
-
- - -
-
-

${HeaderSystemDlnaProfiles}

-
- -

${SystemDlnaProfilesHelp}

-
-
-
-
-
-
diff --git a/src/controllers/dashboard/dlna/profiles.js b/src/controllers/dashboard/dlna/profiles.js deleted file mode 100644 index f69a0c6bf0..0000000000 --- a/src/controllers/dashboard/dlna/profiles.js +++ /dev/null @@ -1,93 +0,0 @@ -import escapeHtml from 'escape-html'; -import 'jquery'; -import globalize from '../../../scripts/globalize'; -import loading from '../../../components/loading/loading'; -import libraryMenu from '../../../scripts/libraryMenu'; -import '../../../components/listview/listview.scss'; -import '../../../elements/emby-button/emby-button'; -import confirm from '../../../components/confirm/confirm'; - -function loadProfiles(page) { - loading.show(); - ApiClient.getJSON(ApiClient.getUrl('Dlna/ProfileInfos')).then(function (result) { - renderUserProfiles(page, result); - renderSystemProfiles(page, result); - loading.hide(); - }); -} - -function renderUserProfiles(page, profiles) { - renderProfiles(page, page.querySelector('.customProfiles'), profiles.filter(function (p) { - return p.Type == 'User'; - })); -} - -function renderSystemProfiles(page, profiles) { - renderProfiles(page, page.querySelector('.systemProfiles'), profiles.filter(function (p) { - return p.Type == 'System'; - })); -} - -function renderProfiles(page, element, profiles) { - let html = ''; - - if (profiles.length) { - html += '
'; - } - - for (let i = 0, length = profiles.length; i < length; i++) { - const profile = profiles[i]; - html += '
'; - html += ''; - html += ''; - - if (profile.Type == 'User') { - html += ''; - } - - html += '
'; - } - - if (profiles.length) { - html += '
'; - } - - element.innerHTML = html; - $('.btnDeleteProfile', element).on('click', function () { - const id = this.getAttribute('data-profileid'); - deleteProfile(page, id); - }); -} - -function deleteProfile(page, id) { - confirm(globalize.translate('MessageConfirmProfileDeletion'), globalize.translate('HeaderConfirmProfileDeletion')).then(function () { - loading.show(); - ApiClient.ajax({ - type: 'DELETE', - url: ApiClient.getUrl('Dlna/Profiles/' + id) - }).then(function () { - loading.hide(); - loadProfiles(page); - }); - }); -} - -function getTabs() { - return [{ - href: '#/dashboard/dlna', - name: globalize.translate('Settings') - }, { - href: '#/dashboard/dlna/profiles', - name: globalize.translate('TabProfiles') - }]; -} - -$(document).on('pageshow', '#dlnaProfilesPage', function () { - libraryMenu.setTabs('dlna', 1, getTabs); - loadProfiles(this); -}); - diff --git a/src/controllers/dashboard/dlna/settings.html b/src/controllers/dashboard/dlna/settings.html deleted file mode 100644 index 4bf5ffc81c..0000000000 --- a/src/controllers/dashboard/dlna/settings.html +++ /dev/null @@ -1,69 +0,0 @@ -
- -
-
- -
- -
-
-

${Settings}

- ${Help} -
-
- -
- -
${LabelEnableDlnaPlayToHelp}
-
- -
- -
${LabelEnableDlnaDebugLoggingHelp}
-
- -
- -
${LabelEnableDlnaClientDiscoveryIntervalHelp}
-
- -
- -
${LabelEnableDlnaServerHelp}
-
- -
- -
${LabelEnableBlastAliveMessagesHelp}
-
- -
- -
${LabelBlastMessageIntervalHelp}
-
-
- -
${LabelDefaultUserHelp}
-
-
- -
-
- -
-
-
diff --git a/src/controllers/dashboard/dlna/settings.js b/src/controllers/dashboard/dlna/settings.js deleted file mode 100644 index d12b6744af..0000000000 --- a/src/controllers/dashboard/dlna/settings.js +++ /dev/null @@ -1,60 +0,0 @@ -import escapeHtml from 'escape-html'; -import 'jquery'; -import loading from '../../../components/loading/loading'; -import libraryMenu from '../../../scripts/libraryMenu'; -import globalize from '../../../scripts/globalize'; -import Dashboard from '../../../utils/dashboard'; - -function loadPage(page, config, users) { - page.querySelector('#chkEnablePlayTo').checked = config.EnablePlayTo; - page.querySelector('#chkEnableDlnaDebugLogging').checked = config.EnableDebugLog; - $('#txtClientDiscoveryInterval', page).val(config.ClientDiscoveryIntervalSeconds); - $('#chkEnableServer', page).prop('checked', config.EnableServer); - $('#chkBlastAliveMessages', page).prop('checked', config.BlastAliveMessages); - $('#txtBlastInterval', page).val(config.BlastAliveMessageIntervalSeconds); - const usersHtml = users.map(function (u) { - return ''; - }).join(''); - $('#selectUser', page).html(usersHtml).val(config.DefaultUserId || ''); - loading.hide(); -} - -function onSubmit() { - loading.show(); - const form = this; - ApiClient.getNamedConfiguration('dlna').then(function (config) { - config.EnablePlayTo = form.querySelector('#chkEnablePlayTo').checked; - config.EnableDebugLog = form.querySelector('#chkEnableDlnaDebugLogging').checked; - config.ClientDiscoveryIntervalSeconds = $('#txtClientDiscoveryInterval', form).val(); - config.EnableServer = $('#chkEnableServer', form).is(':checked'); - config.BlastAliveMessages = $('#chkBlastAliveMessages', form).is(':checked'); - config.BlastAliveMessageIntervalSeconds = $('#txtBlastInterval', form).val(); - config.DefaultUserId = $('#selectUser', form).val(); - ApiClient.updateNamedConfiguration('dlna', config).then(Dashboard.processServerConfigurationUpdateResult); - }); - return false; -} - -function getTabs() { - return [{ - href: '#/dashboard/dlna', - name: globalize.translate('Settings') - }, { - href: '#/dashboard/dlna/profiles', - name: globalize.translate('TabProfiles') - }]; -} - -$(document).on('pageinit', '#dlnaSettingsPage', function () { - $('.dlnaSettingsForm').off('submit', onSubmit).on('submit', onSubmit); -}).on('pageshow', '#dlnaSettingsPage', function () { - libraryMenu.setTabs('dlna', 0, getTabs); - loading.show(); - const page = this; - const promise1 = ApiClient.getNamedConfiguration('dlna'); - const promise2 = ApiClient.getUsers(); - Promise.all([promise1, promise2]).then(function (responses) { - loadPage(page, responses[0], responses[1]); - }); -}); - diff --git a/src/strings/en-us.json b/src/strings/en-us.json index c7a6fe5edf..bde44f6886 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -165,7 +165,6 @@ "CopyStreamURLSuccess": "URL copied successfully.", "CriticRating": "Critics rating", "Cursive": "Cursive", - "CustomDlnaProfilesHelp": "Create a custom profile to target a new device or override a system profile.", "DailyAt": "Daily at {0}", "Data": "Data", "DateAdded": "Date added", @@ -208,6 +207,7 @@ "DisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons", "DisplayMissingEpisodesWithinSeasonsHelp": "This must also be enabled for TV libraries in the server configuration.", "DisplayModeHelp": "Select the layout style you want for the interface.", + "DlnaMovedMessage": "The DLNA functionality has moved to a plugin.", "DoNotRecord": "Do not record", "Down": "Down", "Download": "Download", @@ -338,21 +338,15 @@ "HeaderCastAndCrew": "Cast & Crew", "HeaderChannelAccess": "Channel Access", "HeaderChapterImages": "Chapter Images", - "HeaderCodecProfile": "Codec Profile", - "HeaderCodecProfileHelp": "Codec profiles indicate the limitations of a device when playing specific codecs. If a limitation applies then the media will be transcoded, even if the codec is configured for direct playback.", "HeaderConfigureRemoteAccess": "Set up Remote Access", "HeaderConfirmPluginInstallation": "Confirm Plugin Installation", "HeaderConfirmRepositoryInstallation": "Confirm Plugin Repository Installation", - "HeaderConfirmProfileDeletion": "Confirm Profile Deletion", "HeaderConfirmRevokeApiKey": "Revoke API Key", "HeaderConnectionFailure": "Connection Failure", "HeaderConnectToServer": "Connect to Server", - "HeaderContainerProfile": "Container Profile", - "HeaderContainerProfileHelp": "Container profiles indicate the limitations of a device when playing specific formats. If a limitation applies then the media will be transcoded, even if the format is configured for direct playback.", "HeaderContinueListening": "Continue Listening", "HeaderContinueWatching": "Continue Watching", "HeaderContinueReading": "Continue Reading", - "HeaderCustomDlnaProfiles": "Custom Profiles", "HeaderDateIssued": "Date Issued", "HeaderDefaultRecordingSettings": "Default Recording Settings", "HeaderDeleteDevice": "Delete Device", @@ -365,8 +359,6 @@ "HeaderDeveloperInfo": "Developer Info", "HeaderDeviceAccess": "Device Access", "HeaderDevices": "Devices", - "HeaderDirectPlayProfile": "Direct Playback Profile", - "HeaderDirectPlayProfileHelp": "Add direct playback profiles to indicate which formats the device can handle natively.", "HeaderDownloadSync": "Download & Sync", "HeaderDummyChapter": "Chapter Images", "HeaderDVR": "DVR", @@ -383,14 +375,9 @@ "HeaderFrequentlyPlayed": "Frequently Played", "HeaderGuestCast": "Guest Stars", "HeaderGuideProviders": "TV Guide Data Providers", - "HeaderHttpHeaders": "HTTP Headers", "HeaderHttpsSettings": "HTTPS Settings", - "HeaderIdentification": "Identification", - "HeaderIdentificationCriteriaHelp": "Enter at least one identification criteria.", - "HeaderIdentificationHeader": "Identification Header", "HeaderIdentifyItemHelp": "Enter one or more search criteria. Remove criteria to increase search results.", "HeaderImageOptions": "Image Options", - "HeaderImageSettings": "Image Settings", "HeaderInstall": "Install", "HeaderInstantMix": "Instant Mix", "HeaderKeepRecording": "Keep Recording", @@ -439,8 +426,6 @@ "HeaderPluginInstallation": "Plugin Installation", "HeaderPortRanges": "Firewall and Proxy Settings", "HeaderPreferredMetadataLanguage": "Preferred Metadata Language", - "HeaderProfileInformation": "Profile Information", - "HeaderProfileServerSettingsHelp": "These values control how the server will present itself to clients.", "HeaderRecentlyPlayed": "Recently Played", "HeaderRecordingMetadataSaving": "Recording Metadata", "HeaderRecordingOptions": "Recording Options", @@ -449,8 +434,6 @@ "HeaderRemoteControl": "Remote Control", "HeaderRemoveMediaFolder": "Remove Media Folder", "HeaderRemoveMediaLocation": "Remove Media Location", - "HeaderResponseProfile": "Response Profile", - "HeaderResponseProfileHelp": "Response profiles provide a way to customize information sent to the device when playing certain kinds of media.", "HeaderRevisionHistory": "Revision History", "HeaderRunningTasks": "Running Tasks", "HeaderScenes": "Scenes", @@ -468,7 +451,6 @@ "HeaderSeriesOptions": "Series Options", "HeaderSeriesStatus": "Series Status", "HeaderServerAddressSettings": "Server Address Settings", - "HeaderServerSettings": "Server Settings", "HeaderSetupLibrary": "Setup your media libraries", "HeaderSortBy": "Sort By", "HeaderSortOrder": "Sort Order", @@ -478,20 +460,14 @@ "HeaderStopRecording": "Stop Recording", "HeaderSubtitleAppearance": "Subtitle Appearance", "HeaderSubtitleDownloads": "Subtitle Downloads", - "HeaderSubtitleProfile": "Subtitle Profile", - "HeaderSubtitleProfiles": "Subtitle Profiles", - "HeaderSubtitleProfilesHelp": "Subtitle profiles describe the subtitle formats supported by the device.", "HeaderSyncPlayEnabled": "SyncPlay enabled", "HeaderSyncPlaySelectGroup": "Join a group", "HeaderSyncPlaySettings": "SyncPlay Settings", "HeaderSyncPlayPlaybackSettings": "Playback", "HeaderSyncPlayTimeSyncSettings": "Time sync", - "HeaderSystemDlnaProfiles": "System Profiles", "HeaderTaskTriggers": "Task Triggers", "HeaderThisUserIsCurrentlyDisabled": "This user is currently disabled", "HeaderTracks": "Tracks", - "HeaderTranscodingProfile": "Transcoding Profile", - "HeaderTranscodingProfileHelp": "Add transcoding profiles to indicate which formats should be used when transcoding is required.", "HeaderTunerDevices": "Tuner Devices", "HeaderTuners": "Tuners", "HeaderTypeImageFetchers": "Image fetchers ({0})", @@ -506,9 +482,6 @@ "HeaderVideos": "Videos", "HeaderVideoType": "Video Type", "HeaderVideoTypes": "Video Types", - "HeaderXmlDocumentAttribute": "XML Document Attribute", - "HeaderXmlDocumentAttributes": "XML Document Attributes", - "HeaderXmlSettings": "XML Settings", "HeaderYears": "Years", "Help": "Help", "Hide": "Hide", @@ -541,12 +514,7 @@ "LabelAirsBeforeSeason": "Airs before season", "LabelAirTime": "Air time", "LabelAlbum": "Album", - "LabelAlbumArtHelp": "PN used for album art, within the 'dlna:profileID' attribute on 'upnp:albumArtURI'. Some devices require a specific value, regardless of the size of the image.", "LabelAlbumArtists": "Album artists", - "LabelAlbumArtMaxHeight": "Album art max height", - "LabelAlbumArtMaxResHelp": "Maximum resolution of album art exposed via the 'upnp:albumArtURI' property.", - "LabelAlbumArtMaxWidth": "Album art max width", - "LabelAlbumArtPN": "Album art PN", "LabelAlbumGain": "Album Gain", "LabelAllowedRemoteAddresses": "Remote IP address filter", "LabelAllowedRemoteAddressesMode": "Remote IP address filter mode", @@ -618,12 +586,8 @@ "LabelDay": "Day of week", "LabelDeathDate": "Death date", "LabelDefaultScreen": "Default screen", - "LabelDefaultUser": "Default user", - "LabelDefaultUserHelp": "Determine which user library should be displayed on connected devices. This can be overridden for each device using profiles.", "LabelDeinterlaceMethod": "Deinterlacing method", "LabelDeveloper": "Developer", - "LabelDeviceDescription": "Device description", - "LabelDidlMode": "DIDL mode", "LabelDisableCustomCss": "Disable custom CSS code for theming/branding provided from the server.", "LabelDiscNumber": "Disc number", "LabelDisplayLanguage": "Display language", @@ -645,22 +609,10 @@ "LabelChapterImageResolution": "Resolution", "LabelChapterImageResolutionHelp": "The resolution of the extracted chapter images. Changing this will have no effect on existing dummy chapters.", "LabelDynamicExternalId": "{0} Id", - "LabelEmbedAlbumArtDidl": "Embed album art in DIDL", - "LabelEmbedAlbumArtDidlHelp": "Some devices prefer this method for getting the album art. Others may fail to play with this option enabled.", "LabelEnableAudioVbr": "Enable VBR audio encoding", "LabelEnableAudioVbrHelp": "Variable bitrate offers better quality to average bitrate ratio, but in some rare cases may cause buffering and compatibility issues.", "LabelEnableAutomaticPortMap": "Enable automatic port mapping", "LabelEnableAutomaticPortMapHelp": "Automatically forward public ports on your router to local ports on your server via UPnP. This may not work with some router models or network configurations. Changes will not apply until after a server restart.", - "LabelEnableBlastAliveMessages": "Blast alive messages", - "LabelEnableBlastAliveMessagesHelp": "Enable this if the server is not detected reliably by other UPnP devices on your network.", - "LabelEnableDlnaClientDiscoveryInterval": "Client discovery interval", - "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determine the duration in seconds between two SSDP searches.", - "LabelEnableDlnaDebugLogging": "Enable DLNA debug logging", - "LabelEnableDlnaDebugLoggingHelp": "Create large log files and should only be used as needed for troubleshooting purposes.", - "LabelEnableDlnaPlayTo": "Enable 'Play To' DLNA feature", - "LabelEnableDlnaPlayToHelp": "Detect devices within your network and offer the ability to control them remotely.", - "LabelEnableDlnaServer": "Enable DLNA server", - "LabelEnableDlnaServerHelp": "Allow UPnP devices on your network to browse and play content.", "LabelEnableHardwareDecodingFor": "Enable hardware decoding for", "LabelEnableHttps": "Enable HTTPS", "LabelEnableHttpsHelp": "Listen on the configured HTTPS port. A valid certificate must also be supplied for this to take effect.", @@ -672,8 +624,6 @@ "LabelEnableLUFSScanHelp": "Clients can normalize audio playback to get equal loudness across tracks. This will make library scans longer and take more resources.", "LabelEnableRealtimeMonitor": "Enable real time monitoring", "LabelEnableRealtimeMonitorHelp": "Changes to files will be processed immediately on supported file systems.", - "LabelEnableSingleImageInDidlLimit": "Limit to single embedded image", - "LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within DIDL.", "LabelEncoderPreset": "Encoding preset", "LabelEndDate": "End date", "LabelEpisodeNumber": "Episode number", @@ -702,10 +652,6 @@ "LabelHomeScreenSectionValue": "Home screen section {0}", "LabelHttpsPort": "Local HTTPS port number", "LabelHttpsPortHelp": "The TCP port number for the HTTPS server.", - "LabelIconMaxHeight": "Icon maximum height", - "LabelIconMaxResHelp": "Maximum resolution of icons exposed via the 'upnp:icon' property.", - "LabelIconMaxWidth": "Icon maximum width", - "LabelIdentificationFieldHelp": "A case-insensitive substring or regex expression.", "LabelImageFetchersHelp": "Enable and rank your preferred image fetchers in order of priority.", "LabelImageType": "Image type", "LabelImportOnlyFavoriteChannels": "Restrict to channels marked as favorite", @@ -741,9 +687,6 @@ "LabelLoginDisclaimer": "Login disclaimer", "LabelLoginDisclaimerHelp": "A message that will be displayed at the bottom of the login page.", "LabelLogs": "Logs", - "LabelManufacturer": "Manufacturer", - "LabelManufacturerUrl": "Manufacturer URL", - "LabelMatchType": "Match type", "LabelMaxAudiobookResume": "Audiobook remaining minutes to resume", "LabelMaxAudiobookResumeHelp": "Titles are assumed fully played if stopped when the remaining duration is less than this value.", "LabelMaxBackdropsPerItem": "Maximum number of backdrops per item", @@ -753,8 +696,6 @@ "LabelMaxParentalRating": "Maximum allowed parental rating", "LabelMaxResumePercentage": "Maximum resume percentage", "LabelMaxResumePercentageHelp": "Titles are assumed fully played if stopped after this time.", - "LabelMaxStreamingBitrate": "Maximum streaming quality", - "LabelMaxStreamingBitrateHelp": "Specify a maximum bitrate when streaming.", "LabelMessageText": "Message text", "LabelMessageTitle": "Message title", "LabelMetadata": "Metadata", @@ -766,7 +707,6 @@ "LabelMetadataReadersHelp": "Rank your preferred local metadata sources in order of priority. The first file found will be read.", "LabelMetadataSavers": "Metadata savers", "LabelMetadataSaversHelp": "Pick the file formats to use when saving your metadata.", - "LabelMethod": "Method", "LabelMinAudiobookResume": "Minimum Audiobook resume in minutes", "LabelMinAudiobookResumeHelp": "Titles are assumed unplayed if stopped before this time.", "LabelMinBackdropDownloadWidth": "Minimum backdrop download width", @@ -774,16 +714,10 @@ "LabelMinResumeDurationHelp": "The shortest video length in seconds that will save playback location and let you resume.", "LabelMinResumePercentage": "Minimum resume percentage", "LabelMinResumePercentageHelp": "Titles are assumed unplayed if stopped before this time.", - "LabelModelDescription": "Model description", - "LabelModelName": "Model name", - "LabelModelNumber": "Model number", - "LabelModelUrl": "Model URL", "LabelMovieCategories": "Movie categories", "LabelMoviePrefix": "Movie prefix", "LabelMoviePrefixHelp": "If a prefix is applied to movie titles, enter it here so the server can handle it properly.", "LabelMovieRecordingPath": "Movie recording path", - "LabelMusicStreamingTranscodingBitrate": "Music transcoding bitrate", - "LabelMusicStreamingTranscodingBitrateHelp": "Specify a maximum bitrate when streaming music.", "LabelName": "Name", "LabelNewName": "New name", "LabelNewPassword": "New password", @@ -823,15 +757,8 @@ "LabelPostProcessorArgumentsHelp": "Use {path} as the path to the recording file.", "LabelPreferredDisplayLanguage": "Preferred display language", "LabelPreferredSubtitleLanguage": "Preferred subtitle language", - "LabelProfileAudioCodecs": "Audio codecs", - "LabelProfileCodecs": "Codecs", - "LabelProfileCodecsHelp": "Separated by comma. This can be left empty to apply to all codecs.", "LabelProfileContainer": "Container", - "LabelProfileContainersHelp": "Separated by comma. This can be left empty to apply to all containers.", - "LabelProfileVideoCodecs": "Video codecs", "LabelProtocol": "Protocol", - "LabelProtocolInfo": "Protocol info", - "LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.", "LabelPublicHttpPort": "Public HTTP port number", "LabelPublicHttpPortHelp": "The public port number that should be mapped to the local HTTP port.", "LabelPublicHttpsPort": "Public HTTPS port number", @@ -862,7 +789,6 @@ "LabelSelectFolderGroups": "Automatically group content from the following folders into views such as 'Movies', 'Music' and 'TV'", "LabelSelectFolderGroupsHelp": "Folders that are unchecked will be displayed by themselves in their own view.", "LabelSelectVersionToInstall": "Select version to install", - "LabelSerialNumber": "Serial number", "LabelSeriesRecordingPath": "Series recording path", "LabelServerHost": "Host", "LabelServerHostHelp": "192.168.1.100:8096 or https://myserver.com", @@ -879,8 +805,6 @@ "LabelSkipIfGraphicalSubsPresentHelp": "Keeping text versions of subtitles will result in more efficient delivery and decrease the likelihood of video transcoding.", "LabelSlowResponseEnabled": "Log a warning message if the server was slow to answer", "LabelSlowResponseTime": "Time in ms after which a response is considered slow", - "LabelSonyAggregationFlags": "Sony aggregation flags", - "LabelSonyAggregationFlagsHelp": "Determine the content of the 'aggregationFlags' element in the 'urn:schemas-sonycom:av' namespace.", "LabelSortBy": "Sort by", "LabelSortName": "Sort name", "LabelSortOrder": "Sort order", @@ -895,10 +819,8 @@ "LabelStopWhenPossible": "Stop when possible", "LabelStreamType": "Stream type", "LabelSubtitleDownloaders": "Subtitle downloaders", - "LabelSubtitleFormatHelp": "Example: srt", "LabelSubtitlePlaybackMode": "Subtitle mode", "LabelSubtitleVerticalPosition": "Vertical position", - "LabelSupportedMediaTypes": "Supported Media Types", "LabelSyncPlayAccess": "SyncPlay access", "LabelSyncPlayAccessCreateAndJoinGroups": "Allow user to create and join groups", "LabelSyncPlayAccessJoinGroups": "Allow user to join groups", @@ -973,8 +895,6 @@ "LabelUnstable": "Unstable", "LabelUser": "User", "LabelUserAgent": "User agent", - "LabelUserLibrary": "User library", - "LabelUserLibraryHelp": "Select which user library to display to the device. Leave empty to inherit the default setting.", "LabelUserLoginAttemptsBeforeLockout": "Failed login tries before user is locked out", "LabelUserMaxActiveSessions": "Maximum number of simultaneous user sessions", "LabelUsername": "Username", @@ -990,10 +910,6 @@ "LabelVideoResolution": "Video resolution", "LabelWeb": "Web", "LabelWebVersion": "Web version", - "LabelXDlnaCap": "Device Capability ID", - "LabelXDlnaCapHelp": "Determine the content of the 'X_DLNACAP' element in the 'urn:schemas-dlna-org:device-1-0' namespace.", - "LabelXDlnaDoc": "Device Class ID", - "LabelXDlnaDocHelp": "Determine the content of the 'X_DLNADOC' element in the 'urn:schemas-dlna-org:device-1-0' namespace.", "LabelYear": "Year", "LabelYoureDone": "You're Done!", "LabelZipCode": "Zip Code", @@ -1070,7 +986,6 @@ "MessageConfirmAppExit": "Do you want to exit?", "MessageConfirmDeleteGuideProvider": "Are you sure you wish to delete this guide provider?", "MessageConfirmDeleteTunerDevice": "Are you sure you wish to delete this device?", - "MessageConfirmProfileDeletion": "Are you sure you wish to delete this profile?", "MessageConfirmRecordingCancellation": "Cancel recording?", "MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?", "MessageConfirmRestart": "Are you sure you wish to restart Jellyfin?", @@ -1215,7 +1130,6 @@ "OptionAutomaticallyGroupSeries": "Automatically merge series that are spread across multiple folders", "OptionAutomaticallyGroupSeriesHelp": "Series that are spread across multiple folders within this library will be automatically merged into a single series.", "OptionBluray": "BD", - "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", "OptionCommunityRating": "Community Rating", "OptionCriticRating": "Critics Rating", "OptionDaily": "Daily", @@ -1230,28 +1144,19 @@ "OptionDisplayFolderView": "Display a folder view to show plain media folders", "OptionDisplayFolderViewHelp": "Display folders alongside your other media libraries. This can be useful if you'd like to have a plain folder view.", "OptionDvd": "DVD", - "OptionEmbedSubtitles": "Embed within container", "OptionEnableAccessFromAllDevices": "Enable access from all devices", "OptionEnableAccessToAllChannels": "Enable access to all channels", "OptionEnableAccessToAllLibraries": "Enable access to all libraries", "OptionEnableExternalContentInSuggestions": "Enable external content in suggestions", "OptionEnableExternalContentInSuggestionsHelp": "Allow internet trailers and live TV programs to be included within suggested content.", "OptionEnableForAllTuners": "Enable for all tuner devices", - "OptionEnableM2tsMode": "Enable M2TS mode", - "OptionEnableM2tsModeHelp": "Enable M2TS mode when encoding to MPEG-TS.", - "OptionEquals": "Equals", - "OptionEstimateContentLength": "Estimate content length when transcoding", "OptionEveryday": "Every day", - "OptionExternallyDownloaded": "External download", "OptionExtractChapterImage": "Enable chapter image extraction", "OptionForceRemoteSourceTranscoding": "Force transcoding of remote media sources such as Live TV", "OptionHasThemeSong": "Theme Song", "OptionHasThemeVideo": "Theme Video", "OptionHideUser": "Hide this user from login screens", "OptionHideUserFromLoginHelp": "Useful for private or hidden administrator accounts. The user will need to sign in manually by entering their username and password.", - "OptionHlsSegmentedSubtitles": "HLS segmented subtitles", - "OptionIgnoreTranscodeByteRangeRequests": "Ignore transcode byte range requests", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "These requests will be honored but will ignore the byte range header.", "OptionImdbRating": "IMDb Rating", "OptionIsHD": "HD", "OptionIsSD": "SD", @@ -1264,27 +1169,16 @@ "OptionNew": "New…", "OptionOnInterval": "On an interval", "OptionParentalRating": "Parental Rating", - "OptionPlainStorageFolders": "Display all folders as plain storage folders", - "OptionPlainStorageFoldersHelp": "All folders are represented in DIDL as 'object.container.storageFolder' instead of a more specific type, such as 'object.container.person.musicArtist'.", - "OptionPlainVideoItems": "Display all videos as plain video items", - "OptionPlainVideoItemsHelp": "All videos are represented in DIDL as 'object.item.videoItem' instead of a more specific type, such as 'object.item.videoItem.movie'.", "OptionPlayCount": "Play Count", "OptionPremiereDate": "Premiere Date", - "OptionProtocolHls": "HTTP Live Streaming (HLS)", - "OptionProtocolHttp": "HTTP", "OptionRandom": "Random", - "OptionRegex": "Regex", "OptionReleaseDate": "Release Date", - "OptionReportByteRangeSeekingWhenTranscoding": "Report that the server supports byte seeking when transcoding", - "OptionReportByteRangeSeekingWhenTranscodingHelp": "This is required for some devices that don't time seek very well.", "OptionRequirePerfectSubtitleMatch": "Only download subtitles that are a perfect match for video files", "OptionRequirePerfectSubtitleMatchHelp": "Requiring a perfect match will filter subtitles to include only those that have been tested and verified with your exact video file. Unchecking this will increase the likelihood of subtitles being downloaded, but will increase the chances of mistimed or incorrect subtitle text.", - "OptionResElement": "'res' element", "OptionResumable": "Resumable", "OptionSaveMetadataAsHidden": "Save metadata and images as hidden files", "OptionSaveMetadataAsHiddenHelp": "Changing this will apply to new metadata saved going forward. Existing metadata files will be updated the next time they are saved by the server.", "OptionSpecialEpisode": "Specials", - "OptionSubstring": "Substring", "OptionTrackName": "Track Name", "OptionTvdbRating": "TheTVDB Rating", "OptionUnairedEpisode": "Unaired Episodes", @@ -1501,14 +1395,10 @@ "Sunday": "Sunday", "SyncPlayAccessHelp": "The SyncPlay feature enables to sync playback with other devices. Select the level of access this user has to the SyncPlay.", "SyncPlayGroupDefaultTitle": "{0}'s group", - "SystemDlnaProfilesHelp": "System profiles are read-only. Changes to a system profile will be saved to a new custom profile.", "TabAccess": "Access", "TabAdvanced": "Advanced", "TabCatalog": "Catalog", - "TabCodecs": "Codecs", - "TabContainers": "Containers", "TabDashboard": "Dashboard", - "TabDirectPlay": "Direct Playback", "TabLatest": "Recently Added", "TabLogs": "Logs", "TabMusic": "Music", @@ -1519,9 +1409,7 @@ "TabOther": "Other", "TabParentalControl": "Parental Control", "TabPlugins": "Plugins", - "TabProfiles": "Profiles", "TabRepositories": "Repositories", - "TabResponses": "Responses", "TabScheduledTasks": "Scheduled Tasks", "TabServer": "Server", "TabStreaming": "Streaming", @@ -1578,10 +1466,6 @@ "UserMenu": "User Menu", "UserProfilesIntro": "Jellyfin includes support for user profiles with granular display settings, play state, and parental controls.", "ValueAlbumCount": "{0} albums", - "ValueAudioCodec": "Audio Codec: {0}", - "ValueCodec": "Codec: {0}", - "ValueConditions": "Conditions: {0}", - "ValueContainer": "Container: {0}", "ValueDiscNumber": "Disc {0}", "ValueEpisodeCount": "{0} episodes", "ValueMinutes": "{0} min", @@ -1599,7 +1483,6 @@ "ValueSpecialEpisodeName": "Special - {0}", "ValueTimeLimitMultiHour": "Time limit: {0} hours", "ValueTimeLimitSingleHour": "Time limit: 1 hour", - "ValueVideoCodec": "Video Codec: {0}", "Vertical": "Vertical", "Video": "Video", "VideoAudio": "Video Audio", @@ -1615,7 +1498,6 @@ "WriteAccessRequired": "Jellyfin requires write access to this folder. Please ensure write access and try again.", "Writer": "Writer", "Writers": "Writers", - "XmlDocumentAttributeListHelp": "These attributes are applied to the root element of every XML response.", "XmlTvKidsCategoriesHelp": "Programs with these categories will be displayed as programs for children. Separate multiple with '|'.", "XmlTvMovieCategoriesHelp": "Programs with these categories will be displayed as movies. Separate multiple with '|'.", "XmlTvNewsCategoriesHelp": "Programs with these categories will be displayed as news programs. Separate multiple with '|'.", From ec1f016d5b2a1e92b09047b742888a9a7eadaa84 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Mon, 4 Mar 2024 13:49:29 -0500 Subject: [PATCH 56/90] Update notifications page ui --- src/apps/dashboard/routes/notifications.tsx | 38 ++++++++++----------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/apps/dashboard/routes/notifications.tsx b/src/apps/dashboard/routes/notifications.tsx index 6f673c753f..7642ee033b 100644 --- a/src/apps/dashboard/routes/notifications.tsx +++ b/src/apps/dashboard/routes/notifications.tsx @@ -1,23 +1,13 @@ +import Alert from '@mui/material/Alert/Alert'; +import Box from '@mui/material/Box/Box'; +import Button from '@mui/material/Button/Button'; import React from 'react'; +import { Link } from 'react-router-dom'; import Page from 'components/Page'; import globalize from 'scripts/globalize'; -const PluginLink = () => ( -
+
+ +
${ChannelHeightHelp}
+
diff --git a/src/strings/en-us.json b/src/strings/en-us.json index bde44f6886..c2af890818 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -134,6 +134,7 @@ "Categories": "Categories", "ChangingMetadataImageSettingsNewContent": "Changes to metadata or artwork downloading settings will only apply to new content added to your library. To apply the changes to existing titles, you'll need to refresh their metadata manually.", "ChannelAccessHelp": "Select the channels to share with this user. Administrators will be able to edit all channels using the metadata manager.", + "ChannelHeightHelp": "Set height of video received on this channel. 720 or greater will mark this channel as HD.", "ChannelNameOnly": "Channel {0} only", "ChannelNumber": "Channel number", "Channels": "Channels", @@ -648,6 +649,7 @@ "LabelHardwareAccelerationType": "Hardware acceleration", "LabelHardwareAccelerationTypeHelp": "Hardware acceleration requires additional configuration.", "LabelHardwareEncoding": "Hardware encoding", + "LabelHeight": "Height", "LabelHomeNetworkQuality": "Home network quality", "LabelHomeScreenSectionValue": "Home screen section {0}", "LabelHttpsPort": "Local HTTPS port number", From 420ff05d916cb07fed41adcaf799cb7baa396e54 Mon Sep 17 00:00:00 2001 From: SenorSmartyPants Date: Thu, 30 Mar 2023 13:43:49 -0500 Subject: [PATCH 60/90] Add dropdown with common broadcast video heights --- src/components/metadataEditor/metadataEditor.js | 4 ++-- .../metadataEditor/metadataEditor.template.html | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/metadataEditor/metadataEditor.js b/src/components/metadataEditor/metadataEditor.js index 8dbb103355..c3c50688fc 100644 --- a/src/components/metadataEditor/metadataEditor.js +++ b/src/components/metadataEditor/metadataEditor.js @@ -153,7 +153,7 @@ function onSubmit(e) { DateCreated: getDateValue(form, '#txtDateAdded', 'DateCreated'), EndDate: getDateValue(form, '#txtEndDate', 'EndDate'), ProductionYear: form.querySelector('#txtProductionYear').value, - Height: form.querySelector('#txtHeight').value, + Height: form.querySelector('#selectHeight').value, AspectRatio: form.querySelector('#txtOriginalAspectRatio').value, Video3DFormat: form.querySelector('#select3dFormat').value, @@ -835,7 +835,7 @@ function fillItemInfo(context, item, parentalRatingOptions) { const placeofBirth = item.ProductionLocations?.length ? item.ProductionLocations[0] : ''; context.querySelector('#txtPlaceOfBirth').value = placeofBirth; - context.querySelector('#txtHeight').value = item.Height || ''; + context.querySelector('#selectHeight').value = item.Height || ''; context.querySelector('#txtOriginalAspectRatio').value = item.AspectRatio || ''; diff --git a/src/components/metadataEditor/metadataEditor.template.html b/src/components/metadataEditor/metadataEditor.template.html index f56acb73f1..7f7acd7e3d 100644 --- a/src/components/metadataEditor/metadataEditor.template.html +++ b/src/components/metadataEditor/metadataEditor.template.html @@ -142,8 +142,15 @@
-
- +
+
${ChannelHeightHelp}
From 8443b5d6db5e3836bfc2b7a231962428cd072b44 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 5 Mar 2024 02:31:53 -0500 Subject: [PATCH 61/90] Update channel resolution setting text --- .../metadataEditor/metadataEditor.template.html | 13 ++++++------- src/strings/en-us.json | 7 +++++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/metadataEditor/metadataEditor.template.html b/src/components/metadataEditor/metadataEditor.template.html index 7f7acd7e3d..6c01c2fc07 100644 --- a/src/components/metadataEditor/metadataEditor.template.html +++ b/src/components/metadataEditor/metadataEditor.template.html @@ -143,15 +143,14 @@
- - - - - - + + + + + -
${ChannelHeightHelp}
diff --git a/src/strings/en-us.json b/src/strings/en-us.json index c2af890818..7bc81572d4 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -134,7 +134,11 @@ "Categories": "Categories", "ChangingMetadataImageSettingsNewContent": "Changes to metadata or artwork downloading settings will only apply to new content added to your library. To apply the changes to existing titles, you'll need to refresh their metadata manually.", "ChannelAccessHelp": "Select the channels to share with this user. Administrators will be able to edit all channels using the metadata manager.", - "ChannelHeightHelp": "Set height of video received on this channel. 720 or greater will mark this channel as HD.", + "ChannelResolutionSD": "SD", + "ChannelResolutionSDPAL": "SD (PAL)", + "ChannelResolutionHD": "HD", + "ChannelResolutionFullHD": "Full HD", + "ChannelResolutionUHD4K": "UHD (4K)", "ChannelNameOnly": "Channel {0} only", "ChannelNumber": "Channel number", "Channels": "Channels", @@ -649,7 +653,6 @@ "LabelHardwareAccelerationType": "Hardware acceleration", "LabelHardwareAccelerationTypeHelp": "Hardware acceleration requires additional configuration.", "LabelHardwareEncoding": "Hardware encoding", - "LabelHeight": "Height", "LabelHomeNetworkQuality": "Home network quality", "LabelHomeScreenSectionValue": "Home screen section {0}", "LabelHttpsPort": "Local HTTPS port number", From 2e85cb9e33917a3251c40bbd5496b382dc071f9c Mon Sep 17 00:00:00 2001 From: Kityn Date: Tue, 5 Mar 2024 07:29:23 +0000 Subject: [PATCH 62/90] Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pl/ --- src/strings/pl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/pl.json b/src/strings/pl.json index b0436984d0..4c80c3f4a9 100644 --- a/src/strings/pl.json +++ b/src/strings/pl.json @@ -1786,5 +1786,6 @@ "LabelBuildVersion": "Wersja kompilacji", "LabelServerVersion": "Wersja serwera", "LabelWebVersion": "Wersja sieciowa", - "ButtonEditUser": "Edytuj użytkownika" + "ButtonEditUser": "Edytuj użytkownika", + "DlnaMovedMessage": "Funkcjonalność DLNA została przeniesiona do wtyczki." } From 1d38aa120888cc9199646ceecea880adcae017d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Mar=C3=B3y?= Date: Mon, 19 Feb 2024 10:22:02 +0000 Subject: [PATCH 63/90] Lower web0s version needed for audiotracks support --- src/scripts/browserDeviceProfile.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/scripts/browserDeviceProfile.js b/src/scripts/browserDeviceProfile.js index 51097a475b..b69ff1dd3d 100644 --- a/src/scripts/browserDeviceProfile.js +++ b/src/scripts/browserDeviceProfile.js @@ -394,8 +394,7 @@ export function canPlaySecondaryAudio(videoTestElement) { && !browser.firefox // It seems to work on Tizen 5.5+ (2020, Chrome 69+). See https://developer.tizen.org/forums/web-application-development/video-tag-not-work-audiotracks && (browser.tizenVersion >= 5.5 || !browser.tizen) - // Assume webOS 5+ (2020, Chrome 68+) supports secondary audio like Tizen 5.5+ - && (browser.web0sVersion >= 5.0 || !browser.web0sVersion); + && (browser.web0sVersion >= 4.0 || !browser.web0sVersion); } export default function (options) { From b62c045868583dd7f9dae5c4d9acc237b76b3c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Mar=C3=B3y?= Date: Mon, 19 Feb 2024 10:22:34 +0000 Subject: [PATCH 64/90] Update CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index daec591172..f30c32b359 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -79,6 +79,7 @@ - [Kevin Tan (Valius)](https://github.com/valius) - [Rasmus Krämer](https://github.com/rasmuslos) - [ntarelix](https://github.com/ntarelix) +- [András Maróy](https://github.com/andrasmaroy) ## Emby Contributors From ed758c6320a31ca955e833cdd6cb4a66c682315a Mon Sep 17 00:00:00 2001 From: foXaCe Date: Tue, 5 Mar 2024 18:50:53 +0000 Subject: [PATCH 65/90] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 8ec284a83c..71a68765ec 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1785,5 +1785,7 @@ "HeaderAllRecordings": "Tous les enregistrements", "LabelBuildVersion": "Numéro de build", "LabelServerVersion": "Version du serveur", - "LabelWebVersion": "Version web" + "LabelWebVersion": "Version web", + "ButtonEditUser": "Modifier l'utilisateur", + "DlnaMovedMessage": "La fonctionnalité DLNA a été déplacée vers un plugin." } From 045c950ce6438c5e16d5d2a1d8b1ea3ef88fefef Mon Sep 17 00:00:00 2001 From: Adrian Sandu Date: Wed, 14 Feb 2024 14:22:13 +0200 Subject: [PATCH 66/90] Make the server field ignore the trailing slash. Useful on the tizen build, if a trailing slash is added the error message is not helping. --- src/controllers/session/addServer/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/session/addServer/index.js b/src/controllers/session/addServer/index.js index be339ca978..47ebfc6af6 100644 --- a/src/controllers/session/addServer/index.js +++ b/src/controllers/session/addServer/index.js @@ -36,7 +36,7 @@ function handleConnectionResult(page, result) { function submitServer(page) { loading.show(); - const host = page.querySelector('#txtServerHost').value; + const host = page.querySelector('#txtServerHost').value.replace(/\/+$/, ''); ServerConnections.connectToAddress(host, { enableAutoLogin: appSettings.enableAutoLogin() }).then(function(result) { From 53705680e22920e3f53515a1a5777861ffccf9ba Mon Sep 17 00:00:00 2001 From: Gurmeet Athwal Date: Wed, 6 Mar 2024 14:22:53 +0000 Subject: [PATCH 67/90] Translated using Weblate (Hindi) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/hi/ --- src/strings/hi-in.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/strings/hi-in.json b/src/strings/hi-in.json index 7901b4c5ee..247d7fcb0c 100644 --- a/src/strings/hi-in.json +++ b/src/strings/hi-in.json @@ -56,7 +56,7 @@ "UnsupportedPlayback": "Jellyfin DRM द्वारा संरक्षित सामग्री को डिक्रिप्ट नहीं कर सकता है, लेकिन सभी सामग्री की परवाह किए बिना, संरक्षित शीर्षकों सहित प्रयास किया जाएगा। एन्क्रिप्शन या अन्य असमर्थित सुविधाओं जैसे इंटरेक्टिव शीर्षक के कारण कुछ फाइलें पूरी तरह से काली दिखाई दे सकती हैं।", "BoxRear": "बॉक्स (पीछे)", "Box": "बॉक्स", - "Books": "पुस्तकों", + "Books": "पुस्तकें", "BookLibraryHelp": "ऑडियो और पाठ्य पुस्तकें समर्थित हैं। {0} पुस्तक नामकरण गाइड {1} की समीक्षा करें।", "Blacklist": "काला सूची में डालना", "BirthPlaceValue": "जन्म स्थान: {0}", @@ -72,7 +72,7 @@ "AskAdminToCreateLibrary": "लाइब्रेरी बनाने के लिए किसी व्यवस्थापक से पूछें।", "Ascending": "आरोही", "AsManyAsPossible": "जितने अधिक संभव हों", - "Artists": "कलाकारों", + "Artists": "कलाकार", "Artist": "कलाकार", "Art": "कला", "AroundTime": "लगभग {0}", @@ -102,7 +102,7 @@ "Folders": "फ़ोल्डरें", "Favorites": "पसंदीदा", "Default": "प्राथमिक", - "Collections": "संग्रहों", + "Collections": "संग्रह", "Channels": "चैनल", "Movies": "फ़िल्म", "ButtonActivate": "सक्रिय", From 9d03410edb12f9d4800aca2c1f5ad30b77176074 Mon Sep 17 00:00:00 2001 From: stanol Date: Wed, 6 Mar 2024 13:09:13 +0000 Subject: [PATCH 68/90] Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/uk/ --- src/strings/uk.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/uk.json b/src/strings/uk.json index 241873c27a..98b18ecf63 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -1783,5 +1783,6 @@ "LabelBuildVersion": "Версія збірки", "LabelServerVersion": "Версія сервера", "LabelWebVersion": "Версія вебу", - "ButtonEditUser": "Редагувати користувача" + "ButtonEditUser": "Редагувати користувача", + "DlnaMovedMessage": "Функціональність DLNA перенесено у плагін." } From 276a51767255af506ff29c5bb9c7342bd805bd2c Mon Sep 17 00:00:00 2001 From: Gurmeet Athwal Date: Wed, 6 Mar 2024 14:50:06 +0000 Subject: [PATCH 69/90] Translated using Weblate (Hindi) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/hi/ --- src/strings/hi-in.json | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/strings/hi-in.json b/src/strings/hi-in.json index 247d7fcb0c..d4fff81e4e 100644 --- a/src/strings/hi-in.json +++ b/src/strings/hi-in.json @@ -80,10 +80,10 @@ "AnyLanguage": "कोई भी भाषा", "AlwaysPlaySubtitlesHelp": "भाषा की वरीयता से मेल खाने वाले उपशीर्षक ऑडियो भाषा की परवाह किए बिना लोड किए जाएंगे।", "AlwaysPlaySubtitles": "हमेशा खेलो", - "AllowedRemoteAddressesHelp": "कोमा ने नेटवर्क के लिए आईपी पते या आईपी / नेटमास्क प्रविष्टियों की सूची को अलग कर दिया है जिन्हें दूरस्थ रूप से कनेक्ट करने की अनुमति दी जाएगी। यदि खाली छोड़ दिया जाता है, तो सभी दूरस्थ पते की अनुमति दी जाएगी।", + "AllowedRemoteAddressesHelp": "नेटवर्क के लिए आईपी पतों आईपी/नेटमास्क एंट्रीज़ की कॉमा विभाजित सूची जो रिमोट रूप से कनेक्ट करने की अनुमति देगा। यदि खाली छोड़ा गया है, तो सभी रिमोट पतों को अनुमति दी जाएगी।", "AllowRemoteAccessHelp": "अनियंत्रित होने पर, सभी दूरस्थ कनेक्शन अवरुद्ध हो जाएंगे।", "AllowRemoteAccess": "इस सर्वर को असमीप संपर्क की अनुमति दें", - "AllowFfmpegThrottlingHelp": "जब एक ट्रांसकोड या रीमूक्स वर्तमान प्लेबैक स्थिति से काफी आगे हो जाता है, तो प्रक्रिया को रोकें ताकि यह कम संसाधनों का उपभोग करेगा। अक्सर मांग किए बिना देखने पर यह सबसे उपयोगी है। यदि आप प्लेबैक समस्याओं का अनुभव करते हैं तो इसे बंद कर दें।", + "AllowFfmpegThrottlingHelp": "जब कोई ट्रांसकोड या रीमक्स वर्तमान प्लेबैक स्थिति से काफी आगे निकल जाता है, तो प्रक्रिया को रोक दें ताकि यह कम संसाधनों का उपभोग करे। अक्सर बिना खोजे देखते समय यह सबसे उपयोगी होता है। यदि आप प्लेबैक समस्याओं का अनुभव करते हैं तो इसे बंद कर दें।", "AllowFfmpegThrottling": "थ्रोटल ट्रांसकोड", "AllowOnTheFlySubtitleExtractionHelp": "वीडियो ट्रांसकोडिंग को रोकने में मदद करने के लिए एंबेडेड सबटाइटल वीडियो से निकाले जा सकते हैं और सादे पाठ में ग्राहकों तक पहुंचाए जाते हैं। कुछ प्रणालियों पर यह एक लंबा समय ले सकता है और निष्कर्षण प्रक्रिया के दौरान वीडियो प्लेबैक को स्टाल करने का कारण बन सकता है। जब वे क्लाइंट डिवाइस द्वारा मूल रूप से समर्थित नहीं होते हैं, तो वीडियो ट्रांसकोडिंग के साथ जले हुए एम्बेडेड उपशीर्षक को अक्षम करें।", "AlbumArtist": "एल्बम कलाकार", @@ -96,10 +96,10 @@ "BurnSubtitlesHelp": "निर्धारित करता है कि वीडियो ट्रांसकोडिंग करते समय सर्वर को उपशीर्षक बर्न-इन करना चाहिए। इससे बचने से प्रदर्शन में बहुत सुधार होगा। छवि आधारित उपशीर्षक (VOBSUB, PGS, SUB, IDX, …) एवं ASS अथवा SSA जैसे उपशीर्षक बर्न-इन करने के लिए ऑटो का चयन करें।", "ButtonRemove": "हटाना", "ButtonOpen": "खोलो", - "HeaderContinueWatching": "देखते रहिए", + "HeaderContinueWatching": "देखना जारी रखें", "HeaderAlbumArtists": "एल्बम कलाकार", - "Genres": "शैली", - "Folders": "फ़ोल्डरें", + "Genres": "शैलियां", + "Folders": "फ़ोल्डर", "Favorites": "पसंदीदा", "Default": "प्राथमिक", "Collections": "संग्रह", @@ -151,9 +151,11 @@ "Shows": "शो", "ValueSpecialEpisodeName": "विशेष - {0}", "Sync": "समाकलयति", - "AllowCollectionManagement": "इस यूजर को कलेक्शन परिवर्तन करने की अनुमति दें", + "AllowCollectionManagement": "इस यूजर को संग्रह प्रबंधित करने की अनुमति दें", "AllowSegmentDeletion": "खंड हटाएँ", "AllowSegmentDeletionHelp": "क्लाइंट को भेजे जाने के बाद पुराने सेगमेंट हटा दें। यह संपूर्ण ट्रांसकोड की गई फ़ाइल को डिस्क पर स्टोर करने से रोकता है। केवल थ्रॉटलिंग सक्षम होने पर ही काम करेगा। यदि आप प्लेबैक समस्याओं का अनुभव करते हैं तो इसे बंद कर दें।", "LabelThrottleDelaySeconds": "थ्रॉटर बाद", - "LabelSegmentKeepSeconds": "सेगमेंट रखने का समय" + "LabelSegmentKeepSeconds": "सेगमेंट रखने का समय", + "LabelThrottleDelaySecondsHelp": "सेकंड में समय जिसके बाद ट्रांसकोडर को थ्रॉटल कर दिया जाएगा। क्लाइंट के लिए स्वस्थ बफ़र बनाए रखने के लिए पर्याप्त बड़ा होना चाहिए। केवल तभी काम करता है जब थ्रॉटलिंग सक्रिय हो।", + "LabelSegmentKeepSecondsHelp": "किन खंडों को अधिलेखित करने से पहले रखा जाना चाहिए, इसका समय सेकंड में। \"थ्रोटल आफ्टर\" से बड़ा होना चाहिए। केवल तभी काम करता है जब खंड हटाना सक्रिय हो।" } From e13baa00c043a1fda16a0e1568bb9314d06e6128 Mon Sep 17 00:00:00 2001 From: Bas Date: Wed, 6 Mar 2024 21:16:02 +0000 Subject: [PATCH 70/90] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index 5c5757bddb..b4b2a46fb0 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -1784,5 +1784,7 @@ "HeaderAllRecordings": "Alle opnamen", "LabelBuildVersion": "Buildversie", "LabelServerVersion": "Serverversie", - "LabelWebVersion": "Webversie" + "LabelWebVersion": "Webversie", + "ButtonEditUser": "Gebruiker bewerken", + "DlnaMovedMessage": "De DLNA-functionaliteit is verplaatst naar een plug-in." } From 06c3602bd0585fcbc98c859db254ae4831531989 Mon Sep 17 00:00:00 2001 From: Appoxo Date: Wed, 6 Mar 2024 22:44:21 +0000 Subject: [PATCH 71/90] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/de.json b/src/strings/de.json index 3d125d9048..40cecfc975 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1786,5 +1786,6 @@ "LabelBuildVersion": "Build-Version", "LabelServerVersion": "Server-Version", "LabelWebVersion": "Web-Version", - "ButtonEditUser": "Editiere Benutzer" + "ButtonEditUser": "Editiere Benutzer", + "DlnaMovedMessage": "Die DLNA-Funktion wurde in ein Plugin verschoben." } From df5bafbb540189a8458fa2d09c25fb3864afaf65 Mon Sep 17 00:00:00 2001 From: De sousa John Date: Thu, 7 Mar 2024 10:57:39 +0000 Subject: [PATCH 72/90] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 71a68765ec..c84e93a812 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1787,5 +1787,10 @@ "LabelServerVersion": "Version du serveur", "LabelWebVersion": "Version web", "ButtonEditUser": "Modifier l'utilisateur", - "DlnaMovedMessage": "La fonctionnalité DLNA a été déplacée vers un plugin." + "DlnaMovedMessage": "La fonctionnalité DLNA a été déplacée vers un plugin.", + "ChannelResolutionSD": "SD", + "ChannelResolutionSDPAL": "SD (PAL)", + "ChannelResolutionHD": "HD", + "ChannelResolutionFullHD": "Full HD", + "ChannelResolutionUHD4K": "UHD (4K)" } From 86c9e8b1696934350d070a1773147884358a8918 Mon Sep 17 00:00:00 2001 From: Kityn Date: Thu, 7 Mar 2024 10:35:40 +0000 Subject: [PATCH 73/90] Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pl/ --- src/strings/pl.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/strings/pl.json b/src/strings/pl.json index 4c80c3f4a9..82f523c25a 100644 --- a/src/strings/pl.json +++ b/src/strings/pl.json @@ -1787,5 +1787,10 @@ "LabelServerVersion": "Wersja serwera", "LabelWebVersion": "Wersja sieciowa", "ButtonEditUser": "Edytuj użytkownika", - "DlnaMovedMessage": "Funkcjonalność DLNA została przeniesiona do wtyczki." + "DlnaMovedMessage": "Funkcjonalność DLNA została przeniesiona do wtyczki.", + "ChannelResolutionSD": "SD", + "ChannelResolutionSDPAL": "SD (PAL)", + "ChannelResolutionHD": "HD", + "ChannelResolutionFullHD": "Full HD", + "ChannelResolutionUHD4K": "UHD (4K)" } From d907870a80c9b9e78b15321dd5412f2293b145c6 Mon Sep 17 00:00:00 2001 From: atjokine Date: Thu, 7 Mar 2024 11:45:12 +0000 Subject: [PATCH 74/90] Translated using Weblate (Finnish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fi/ --- src/strings/fi.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/strings/fi.json b/src/strings/fi.json index e14e3259dc..e137647a52 100644 --- a/src/strings/fi.json +++ b/src/strings/fi.json @@ -12,7 +12,7 @@ "FileReadCancelled": "Tiedoston luku on peruutettu.", "FileReadError": "Virhe tiedostoa luettaessa.", "LabelAudioLanguagePreference": "Ensisijainen äänen kieli", - "LabelCountry": "Maa", + "LabelCountry": "Maa/Alue", "LabelCurrentPassword": "Nykyinen salasana", "LabelFinish": "Valmis", "LabelLanguage": "Kieli", @@ -1780,5 +1780,12 @@ "LabelSelectAudioNormalization": "Äänenvoimakkuuden normalisointi", "LabelTrackGain": "Kappelkohtainen vahvistus", "SearchResultsEmpty": "Pahoittelut! Haku \"{0}\" ei tuottanut tuloksia", - "HeaderAllRecordings": "Kaikki tallenteet" + "HeaderAllRecordings": "Kaikki tallenteet", + "ButtonEditUser": "Muokkaa käyttäjää", + "ChannelResolutionSD": "peruslaatu", + "ChannelResolutionHD": "teräväpiirto", + "DlnaMovedMessage": "DLNA-ominaisuus on siirtynyt lisäosaan.", + "LabelBuildVersion": "Käännetty versio", + "LabelServerVersion": "Palvelimen versio", + "LabelWebVersion": "Verkko versio" } From 6def645e8da1145f575e392bd88095454a8aeb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Mar=C3=A7al=20Coutinho?= Date: Thu, 7 Mar 2024 16:51:48 +0000 Subject: [PATCH 75/90] Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt_BR/ --- src/strings/pt-br.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/strings/pt-br.json b/src/strings/pt-br.json index ea9da6111c..51b0db327e 100644 --- a/src/strings/pt-br.json +++ b/src/strings/pt-br.json @@ -1779,5 +1779,15 @@ "LabelAlbumGain": "Nivelar volume do álbum", "LabelSelectAudioNormalization": "Normalização de áudio", "LabelTrackGain": "Nivelação de volume de faixa", - "HeaderAllRecordings": "Todas as Gravações" + "HeaderAllRecordings": "Todas as Gravações", + "ButtonEditUser": "Editar usuário", + "ChannelResolutionSD": "SD", + "ChannelResolutionSDPAL": "SD (PAL)", + "ChannelResolutionHD": "HD", + "ChannelResolutionFullHD": "Full HD", + "ChannelResolutionUHD4K": "UHD (4K)", + "DlnaMovedMessage": "A funcionalidade DLNA foi movida para um plugin.", + "LabelBuildVersion": "Versão da build", + "LabelServerVersion": "Versão do servidor", + "LabelWebVersion": "Versão web" } From d101e3b105d3e675e997ec2f4ee92c193ee65263 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Mar 2024 19:01:33 +0000 Subject: [PATCH 76/90] Pin peter-evans/create-pull-request action to a4f52f8 --- .github/workflows/update-sdk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-sdk.yml b/.github/workflows/update-sdk.yml index 50ba172cfa..c60422ca64 100644 --- a/.github/workflows/update-sdk.yml +++ b/.github/workflows/update-sdk.yml @@ -35,7 +35,7 @@ jobs: echo "JF_SDK_VERSION=${VERSION}" >> $GITHUB_ENV - name: Open a pull request - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@a4f52f8033a6168103c2538976c07b467e8163bc # v6 with: token: ${{ secrets.JF_BOT_TOKEN }} commit-message: Update @jellyfin/sdk to ${{env.JF_SDK_VERSION}} From 1935d8e843d8092567ec724c7cffccffd6188627 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Mar 2024 19:04:46 +0000 Subject: [PATCH 77/90] Update CI dependencies --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/pr-suggestions.yml | 2 +- .github/workflows/publish.yml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b7b091be6e..718be72551 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -22,13 +22,13 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Initialize CodeQL - uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 + uses: github/codeql-action/init@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 with: languages: javascript queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 + uses: github/codeql-action/autobuild@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 + uses: github/codeql-action/analyze@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 diff --git a/.github/workflows/pr-suggestions.yml b/.github/workflows/pr-suggestions.yml index 1bcb1d2d27..d5b9425bc6 100644 --- a/.github/workflows/pr-suggestions.yml +++ b/.github/workflows/pr-suggestions.yml @@ -33,6 +33,6 @@ jobs: - name: Run eslint if: ${{ github.repository == 'jellyfin/jellyfin-web' }} - uses: CatChen/eslint-suggestion-action@7bbf6d65396dbcc73d1e053d900eb5745988c11c # v3.1.2 + uses: CatChen/eslint-suggestion-action@8fb7db4e235f7af9fc434349a124034b681d99a3 # v3.1.3 with: github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c26f948d17..3591534c98 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Download workflow artifact - uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0 + uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 with: run_id: ${{ github.event.workflow_run.id }} name: jellyfin-web__prod @@ -47,7 +47,7 @@ jobs: steps: - name: Get PR context - uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0 + uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 id: pr_context with: run_id: ${{ github.event.workflow_run.id }} @@ -88,7 +88,7 @@ jobs: steps: - name: Update job summary in PR comment - uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # v2.4.3 + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }} message: ${{ needs.compose-comment.outputs.msg }} From 6589621d5f847d3acb96bfc8ac5782462a161e49 Mon Sep 17 00:00:00 2001 From: Bas Date: Thu, 7 Mar 2024 18:42:51 +0000 Subject: [PATCH 78/90] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index b4b2a46fb0..5c98ff7971 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -1786,5 +1786,10 @@ "LabelServerVersion": "Serverversie", "LabelWebVersion": "Webversie", "ButtonEditUser": "Gebruiker bewerken", - "DlnaMovedMessage": "De DLNA-functionaliteit is verplaatst naar een plug-in." + "DlnaMovedMessage": "De DLNA-functionaliteit is verplaatst naar een plug-in.", + "ChannelResolutionSD": "SD", + "ChannelResolutionSDPAL": "SD (PAL)", + "ChannelResolutionHD": "HD", + "ChannelResolutionFullHD": "Full HD", + "ChannelResolutionUHD4K": "UHD (4K)" } From af3a1ea37f032d2972c6e531f90caa355c7cf0c3 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Thu, 7 Mar 2024 15:54:12 -0500 Subject: [PATCH 79/90] Use exact versions for action comment --- .github/workflows/update-sdk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-sdk.yml b/.github/workflows/update-sdk.yml index c60422ca64..bd7b285d5c 100644 --- a/.github/workflows/update-sdk.yml +++ b/.github/workflows/update-sdk.yml @@ -35,7 +35,7 @@ jobs: echo "JF_SDK_VERSION=${VERSION}" >> $GITHUB_ENV - name: Open a pull request - uses: peter-evans/create-pull-request@a4f52f8033a6168103c2538976c07b467e8163bc # v6 + uses: peter-evans/create-pull-request@a4f52f8033a6168103c2538976c07b467e8163bc # v6.0.1 with: token: ${{ secrets.JF_BOT_TOKEN }} commit-message: Update @jellyfin/sdk to ${{env.JF_SDK_VERSION}} From e448a21b6fde7b7080fef51042e48401e09f2883 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Mar 2024 21:04:25 +0000 Subject: [PATCH 80/90] Update dependency @emotion/react to v11.11.4 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9baf182e7a..04468282dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "10.9.0", "license": "GPL-2.0-or-later", "dependencies": { - "@emotion/react": "11.11.3", + "@emotion/react": "11.11.4", "@emotion/styled": "11.11.0", "@fontsource/noto-sans": "5.0.18", "@fontsource/noto-sans-hk": "5.0.17", @@ -2955,9 +2955,9 @@ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, "node_modules/@emotion/react": { - "version": "11.11.3", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.3.tgz", - "integrity": "sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==", + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", + "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", @@ -24856,9 +24856,9 @@ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, "@emotion/react": { - "version": "11.11.3", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.3.tgz", - "integrity": "sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==", + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", + "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", "requires": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", diff --git a/package.json b/package.json index 9826b6358d..7ed3a7c1d5 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "worker-loader": "3.0.8" }, "dependencies": { - "@emotion/react": "11.11.3", + "@emotion/react": "11.11.4", "@emotion/styled": "11.11.0", "@fontsource/noto-sans": "5.0.18", "@fontsource/noto-sans-hk": "5.0.17", From b15879d1dea1533f970a3ad2d8424a6de1ef9cbb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Mar 2024 22:18:02 +0000 Subject: [PATCH 81/90] Update dependency @types/loadable__component to v5.13.9 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 04468282dc..d7785c4fa5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,7 +73,7 @@ "@babel/preset-env": "7.23.8", "@babel/preset-react": "7.23.3", "@types/escape-html": "1.0.4", - "@types/loadable__component": "5.13.8", + "@types/loadable__component": "5.13.9", "@types/lodash-es": "4.17.12", "@types/markdown-it": "13.0.7", "@types/react": "17.0.75", @@ -4587,9 +4587,9 @@ "dev": true }, "node_modules/@types/loadable__component": { - "version": "5.13.8", - "resolved": "https://registry.npmjs.org/@types/loadable__component/-/loadable__component-5.13.8.tgz", - "integrity": "sha512-0FF/WihuPkR5IFOHiBzC95bSACvgQNUQCuNy1WF8F/lCBBHgS2SxarIk4CTjWM10A72ovpmXZDRcuAXZNS+/kQ==", + "version": "5.13.9", + "resolved": "https://registry.npmjs.org/@types/loadable__component/-/loadable__component-5.13.9.tgz", + "integrity": "sha512-QWOtIkwZqHNdQj3nixQ8oyihQiTMKZLk/DNuvNxMSbTfxf47w+kqcbnxlUeBgAxdOtW0Dh48dTAIp83iJKtnrQ==", "dev": true, "dependencies": { "@types/react": "*" @@ -25868,9 +25868,9 @@ "dev": true }, "@types/loadable__component": { - "version": "5.13.8", - "resolved": "https://registry.npmjs.org/@types/loadable__component/-/loadable__component-5.13.8.tgz", - "integrity": "sha512-0FF/WihuPkR5IFOHiBzC95bSACvgQNUQCuNy1WF8F/lCBBHgS2SxarIk4CTjWM10A72ovpmXZDRcuAXZNS+/kQ==", + "version": "5.13.9", + "resolved": "https://registry.npmjs.org/@types/loadable__component/-/loadable__component-5.13.9.tgz", + "integrity": "sha512-QWOtIkwZqHNdQj3nixQ8oyihQiTMKZLk/DNuvNxMSbTfxf47w+kqcbnxlUeBgAxdOtW0Dh48dTAIp83iJKtnrQ==", "dev": true, "requires": { "@types/react": "*" diff --git a/package.json b/package.json index 7ed3a7c1d5..b4fa41b5dc 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "@babel/preset-env": "7.23.8", "@babel/preset-react": "7.23.3", "@types/escape-html": "1.0.4", - "@types/loadable__component": "5.13.8", + "@types/loadable__component": "5.13.9", "@types/lodash-es": "4.17.12", "@types/markdown-it": "13.0.7", "@types/react": "17.0.75", From 86f83a6186bc37f42a229f00f4af157fc9e8eab7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Mar 2024 22:40:32 +0000 Subject: [PATCH 82/90] Update dependency @types/sortablejs to v1.15.8 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index ebc5203f0d..a15f5d3d16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -78,7 +78,7 @@ "@types/markdown-it": "13.0.7", "@types/react": "17.0.75", "@types/react-dom": "17.0.25", - "@types/sortablejs": "1.15.7", + "@types/sortablejs": "1.15.8", "@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/parser": "5.62.0", "@uupaa/dynamic-import-polyfill": "1.0.2", @@ -4772,9 +4772,9 @@ } }, "node_modules/@types/sortablejs": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.7.tgz", - "integrity": "sha512-PvgWCx1Lbgm88FdQ6S7OGvLIjWS66mudKPlfdrWil0TjsO5zmoZmzoKiiwRShs1dwPgrlkr0N4ewuy0/+QUXYQ==", + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.8.tgz", + "integrity": "sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==", "dev": true }, "node_modules/@types/unist": { @@ -26076,9 +26076,9 @@ } }, "@types/sortablejs": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.7.tgz", - "integrity": "sha512-PvgWCx1Lbgm88FdQ6S7OGvLIjWS66mudKPlfdrWil0TjsO5zmoZmzoKiiwRShs1dwPgrlkr0N4ewuy0/+QUXYQ==", + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.8.tgz", + "integrity": "sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==", "dev": true }, "@types/unist": { diff --git a/package.json b/package.json index b4fa41b5dc..6b68ce0001 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "@types/markdown-it": "13.0.7", "@types/react": "17.0.75", "@types/react-dom": "17.0.25", - "@types/sortablejs": "1.15.7", + "@types/sortablejs": "1.15.8", "@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/parser": "5.62.0", "@uupaa/dynamic-import-polyfill": "1.0.2", From b75047804d3f8cbff70962bb7681370c5c49fa76 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Mar 2024 23:30:16 +0000 Subject: [PATCH 83/90] Update dependency cssnano to v6.0.5 --- package-lock.json | 595 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 301 insertions(+), 296 deletions(-) diff --git a/package-lock.json b/package-lock.json index a15f5d3d16..33b338c2be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -90,7 +90,7 @@ "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "6.9.1", - "cssnano": "6.0.3", + "cssnano": "6.0.5", "es-check": "7.1.1", "eslint": "8.56.0", "eslint-plugin-compat": "4.2.0", @@ -6354,9 +6354,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -6373,8 +6373,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -6559,9 +6559,9 @@ "dev": true }, "node_modules/caniuse-lite": { - "version": "1.0.30001579", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", - "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==", + "version": "1.0.30001596", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001596.tgz", + "integrity": "sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==", "dev": true, "funding": [ { @@ -7506,13 +7506,13 @@ } }, "node_modules/cssnano": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.3.tgz", - "integrity": "sha512-MRq4CIj8pnyZpcI2qs6wswoYoDD1t0aL28n+41c1Ukcpm56m1h6mCexIHBGjfZfnTqtGSSCP4/fB1ovxgjBOiw==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.5.tgz", + "integrity": "sha512-tpTp/ukgrElwu3ESFY4IvWnGn8eTt8cJhC2aAbtA3lvUlxp6t6UPv8YCLjNnEGiFreT1O0LiOM1U3QyTBVFl2A==", "dev": true, "dependencies": { - "cssnano-preset-default": "^6.0.3", - "lilconfig": "^3.0.0" + "cssnano-preset-default": "^6.0.5", + "lilconfig": "^3.1.1" }, "engines": { "node": "^14 || ^16 || >=18.0" @@ -7526,40 +7526,41 @@ } }, "node_modules/cssnano-preset-default": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.3.tgz", - "integrity": "sha512-4y3H370aZCkT9Ev8P4SO4bZbt+AExeKhh8wTbms/X7OLDo5E7AYUUy6YPxa/uF5Grf+AJwNcCnxKhZynJ6luBA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.0.tgz", + "integrity": "sha512-4DUXZoDj+PI3fRl3MqMjl9DwLGjcsFP4qt+92nLUcN1RGfw2TY+GwNoG2B38Usu1BrcTs8j9pxNfSusmvtSjfg==", "dev": true, "dependencies": { + "browserslist": "^4.23.0", "css-declaration-sorter": "^7.1.1", - "cssnano-utils": "^4.0.1", + "cssnano-utils": "^4.0.2", "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.0.2", - "postcss-convert-values": "^6.0.2", - "postcss-discard-comments": "^6.0.1", - "postcss-discard-duplicates": "^6.0.1", - "postcss-discard-empty": "^6.0.1", - "postcss-discard-overridden": "^6.0.1", - "postcss-merge-longhand": "^6.0.2", - "postcss-merge-rules": "^6.0.3", - "postcss-minify-font-values": "^6.0.1", - "postcss-minify-gradients": "^6.0.1", - "postcss-minify-params": "^6.0.2", - "postcss-minify-selectors": "^6.0.2", - "postcss-normalize-charset": "^6.0.1", - "postcss-normalize-display-values": "^6.0.1", - "postcss-normalize-positions": "^6.0.1", - "postcss-normalize-repeat-style": "^6.0.1", - "postcss-normalize-string": "^6.0.1", - "postcss-normalize-timing-functions": "^6.0.1", - "postcss-normalize-unicode": "^6.0.2", - "postcss-normalize-url": "^6.0.1", - "postcss-normalize-whitespace": "^6.0.1", - "postcss-ordered-values": "^6.0.1", - "postcss-reduce-initial": "^6.0.2", - "postcss-reduce-transforms": "^6.0.1", - "postcss-svgo": "^6.0.2", - "postcss-unique-selectors": "^6.0.2" + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.4", + "postcss-merge-rules": "^6.1.0", + "postcss-minify-font-values": "^6.0.3", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.3", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.3" }, "engines": { "node": "^14 || ^16 || >=18.0" @@ -7569,9 +7570,9 @@ } }, "node_modules/cssnano-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.1.tgz", - "integrity": "sha512-6qQuYDqsGoiXssZ3zct6dcMxiqfT6epy7x4R0TQJadd4LWO3sPR6JH6ZByOvVLoZ6EdwPGgd7+DR1EmX3tiXQQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", "dev": true, "engines": { "node": "^14 || ^16 || >=18.0" @@ -8133,9 +8134,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.623", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.623.tgz", - "integrity": "sha512-lKoz10iCYlP1WtRYdh5MvocQPWVRoI7ysp6qf18bmeBgR8abE6+I2CsfyNKztRDZvhdWc+krKT6wS7Neg8sw3A==", + "version": "1.4.697", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.697.tgz", + "integrity": "sha512-iPS+iUNUrqTkPRFjMYv1FGXIUYhj2K4rc/93nrDsDtQGMUqyRouCq/xABOSOljKbriEiwg0bEQHGaeD4OaU56g==", "dev": true }, "node_modules/emoji-regex": { @@ -12615,12 +12616,15 @@ } }, "node_modules/lilconfig": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", - "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", "dev": true, "engines": { "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { @@ -14395,14 +14399,14 @@ } }, "node_modules/postcss-colormin": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.2.tgz", - "integrity": "sha512-TXKOxs9LWcdYo5cgmcSHPkyrLAh86hX1ijmyy6J8SbOhyv6ua053M3ZAM/0j44UsnQNIWdl8gb5L7xX2htKeLw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", "dev": true, "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.0", "caniuse-api": "^3.0.0", - "colord": "^2.9.1", + "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -14413,12 +14417,12 @@ } }, "node_modules/postcss-convert-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.2.tgz", - "integrity": "sha512-aeBmaTnGQ+NUSVQT8aY0sKyAD/BaLJenEKZ03YK0JnDE1w1Rr8XShoxdal2V2H26xTJKr3v5haByOhJuyT4UYw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", "dev": true, "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -14538,9 +14542,9 @@ } }, "node_modules/postcss-discard-comments": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.1.tgz", - "integrity": "sha512-f1KYNPtqYLUeZGCHQPKzzFtsHaRuECe6jLakf/RjSRqvF5XHLZnM2+fXLhb8Qh/HBFHs3M4cSLb1k3B899RYIg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", "dev": true, "engines": { "node": "^14 || ^16 || >=18.0" @@ -14550,9 +14554,9 @@ } }, "node_modules/postcss-discard-duplicates": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.1.tgz", - "integrity": "sha512-1hvUs76HLYR8zkScbwyJ8oJEugfPV+WchpnA+26fpJ7Smzs51CzGBHC32RS03psuX/2l0l0UKh2StzNxOrKCYg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", "dev": true, "engines": { "node": "^14 || ^16 || >=18.0" @@ -14562,9 +14566,9 @@ } }, "node_modules/postcss-discard-empty": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.1.tgz", - "integrity": "sha512-yitcmKwmVWtNsrrRqGJ7/C0YRy53i0mjexBDQ9zYxDwTWVBgbU4+C9jIZLmQlTDT9zhml+u0OMFJh8+31krmOg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", "dev": true, "engines": { "node": "^14 || ^16 || >=18.0" @@ -14574,9 +14578,9 @@ } }, "node_modules/postcss-discard-overridden": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.1.tgz", - "integrity": "sha512-qs0ehZMMZpSESbRkw1+inkf51kak6OOzNRaoLd/U7Fatp0aN2HQ1rxGOrJvYcRAN9VpX8kUF13R2ofn8OlvFVA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", "dev": true, "engines": { "node": "^14 || ^16 || >=18.0" @@ -15167,13 +15171,13 @@ "dev": true }, "node_modules/postcss-merge-longhand": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.2.tgz", - "integrity": "sha512-+yfVB7gEM8SrCo9w2lCApKIEzrTKl5yS1F4yGhV3kSim6JzbfLGJyhR1B6X+6vOT0U33Mgx7iv4X9MVWuaSAfw==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.4.tgz", + "integrity": "sha512-vAfWGcxUUGlFiPM3nDMZA+/Yo9sbpc3JNkcYZez8FfJDv41Dh7tAgA3QGVTocaHCZZL6aXPXPOaBMJsjujodsA==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.0.2" + "stylehacks": "^6.1.0" }, "engines": { "node": "^14 || ^16 || >=18.0" @@ -15183,14 +15187,14 @@ } }, "node_modules/postcss-merge-rules": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.3.tgz", - "integrity": "sha512-yfkDqSHGohy8sGYIJwBmIGDv4K4/WrJPX355XrxQb/CSsT4Kc/RxDi6akqn5s9bap85AWgv21ArcUWwWdGNSHA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.0.tgz", + "integrity": "sha512-lER+W3Gr6XOvxOYk1Vi/6UsAgKMg6MDBthmvbNqi2XxAk/r9XfhdYZSigfWjuWWn3zYw2wLelvtM8XuAEFqRkA==", "dev": true, "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.0", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.1", + "cssnano-utils": "^4.0.2", "postcss-selector-parser": "^6.0.15" }, "engines": { @@ -15201,9 +15205,9 @@ } }, "node_modules/postcss-minify-font-values": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.1.tgz", - "integrity": "sha512-tIwmF1zUPoN6xOtA/2FgVk1ZKrLcCvE0dpZLtzyyte0j9zUeB8RTbCqrHZGjJlxOvNWKMYtunLrrl7HPOiR46w==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.3.tgz", + "integrity": "sha512-SmAeTA1We5rMnN3F8X9YBNo9bj9xB4KyDHnaNJnBfQIPi+60fNiR9OTRnIaMqkYzAQX0vObIw4Pn0vuKEOettg==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -15216,13 +15220,13 @@ } }, "node_modules/postcss-minify-gradients": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.1.tgz", - "integrity": "sha512-M1RJWVjd6IOLPl1hYiOd5HQHgpp6cvJVLrieQYS9y07Yo8itAr6jaekzJphaJFR0tcg4kRewCk3kna9uHBxn/w==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", "dev": true, "dependencies": { - "colord": "^2.9.1", - "cssnano-utils": "^4.0.1", + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -15233,13 +15237,13 @@ } }, "node_modules/postcss-minify-params": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.2.tgz", - "integrity": "sha512-zwQtbrPEBDj+ApELZ6QylLf2/c5zmASoOuA4DzolyVGdV38iR2I5QRMsZcHkcdkZzxpN8RS4cN7LPskOkTwTZw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", "dev": true, "dependencies": { - "browserslist": "^4.22.2", - "cssnano-utils": "^4.0.1", + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -15250,9 +15254,9 @@ } }, "node_modules/postcss-minify-selectors": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.2.tgz", - "integrity": "sha512-0b+m+w7OAvZejPQdN2GjsXLv5o0jqYHX3aoV0e7RBKPCsB7TYG5KKWBFhGnB/iP3213Ts8c5H4wLPLMm7z28Sg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.3.tgz", + "integrity": "sha512-IcV7ZQJcaXyhx4UBpWZMsinGs2NmiUC60rJSkyvjPCPqhNjVGsrJUM+QhAtCaikZ0w0/AbZuH4wVvF/YMuMhvA==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.15" @@ -15350,9 +15354,9 @@ } }, "node_modules/postcss-normalize-charset": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.1.tgz", - "integrity": "sha512-aW5LbMNRZ+oDV57PF9K+WI1Z8MPnF+A8qbajg/T8PP126YrGX1f9IQx21GI2OlGz7XFJi/fNi0GTbY948XJtXg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", "dev": true, "engines": { "node": "^14 || ^16 || >=18.0" @@ -15362,9 +15366,9 @@ } }, "node_modules/postcss-normalize-display-values": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.1.tgz", - "integrity": "sha512-mc3vxp2bEuCb4LgCcmG1y6lKJu1Co8T+rKHrcbShJwUmKJiEl761qb/QQCfFwlrvSeET3jksolCR/RZuMURudw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -15377,9 +15381,9 @@ } }, "node_modules/postcss-normalize-positions": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.1.tgz", - "integrity": "sha512-HRsq8u/0unKNvm0cvwxcOUEcakFXqZ41fv3FOdPn916XFUrympjr+03oaLkuZENz3HE9RrQE9yU0Xv43ThWjQg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -15392,9 +15396,9 @@ } }, "node_modules/postcss-normalize-repeat-style": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.1.tgz", - "integrity": "sha512-Gbb2nmCy6tTiA7Sh2MBs3fj9W8swonk6lw+dFFeQT68B0Pzwp1kvisJQkdV6rbbMSd9brMlS8I8ts52tAGWmGQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -15407,9 +15411,9 @@ } }, "node_modules/postcss-normalize-string": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.1.tgz", - "integrity": "sha512-5Fhx/+xzALJD9EI26Aq23hXwmv97Zfy2VFrt5PLT8lAhnBIZvmaT5pQk+NuJ/GWj/QWaKSKbnoKDGLbV6qnhXg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -15422,9 +15426,9 @@ } }, "node_modules/postcss-normalize-timing-functions": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.1.tgz", - "integrity": "sha512-4zcczzHqmCU7L5dqTB9rzeqPWRMc0K2HoR+Bfl+FSMbqGBUcP5LRfgcH4BdRtLuzVQK1/FHdFoGT3F7rkEnY+g==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -15437,12 +15441,12 @@ } }, "node_modules/postcss-normalize-unicode": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.2.tgz", - "integrity": "sha512-Ff2VdAYCTGyMUwpevTZPZ4w0+mPjbZzLLyoLh/RMpqUqeQKZ+xMm31hkxBavDcGKcxm6ACzGk0nBfZ8LZkStKA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", "dev": true, "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -15453,9 +15457,9 @@ } }, "node_modules/postcss-normalize-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.1.tgz", - "integrity": "sha512-jEXL15tXSvbjm0yzUV7FBiEXwhIa9H88JOXDGQzmcWoB4mSjZIsmtto066s2iW9FYuIrIF4k04HA2BKAOpbsaQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -15468,9 +15472,9 @@ } }, "node_modules/postcss-normalize-whitespace": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.1.tgz", - "integrity": "sha512-76i3NpWf6bB8UHlVuLRxG4zW2YykF9CTEcq/9LGAiz2qBuX5cBStadkk0jSkg9a9TCIXbMQz7yzrygKoCW9JuA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -15505,12 +15509,12 @@ } }, "node_modules/postcss-ordered-values": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.1.tgz", - "integrity": "sha512-XXbb1O/MW9HdEhnBxitZpPFbIvDgbo9NK4c/5bOfiKpnIGZDoL2xd7/e6jW5DYLsWxBbs+1nZEnVgnjnlFViaA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", "dev": true, "dependencies": { - "cssnano-utils": "^4.0.1", + "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -15689,12 +15693,12 @@ } }, "node_modules/postcss-reduce-initial": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.2.tgz", - "integrity": "sha512-YGKalhNlCLcjcLvjU5nF8FyeCTkCO5UtvJEt0hrPZVCTtRLSOH4z00T1UntQPj4dUmIYZgMj8qK77JbSX95hSw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", "dev": true, "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.0", "caniuse-api": "^3.0.0" }, "engines": { @@ -15705,9 +15709,9 @@ } }, "node_modules/postcss-reduce-transforms": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.1.tgz", - "integrity": "sha512-fUbV81OkUe75JM+VYO1gr/IoA2b/dRiH6HvMwhrIBSUrxq3jNZQZitSnugcTLDi1KkQh1eR/zi+iyxviUNBkcQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -15962,9 +15966,9 @@ } }, "node_modules/postcss-svgo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.2.tgz", - "integrity": "sha512-IH5R9SjkTkh0kfFOQDImyy1+mTCb+E830+9SV1O+AaDcoHTvfsvt6WwJeo7KwcHbFnevZVCsXhDmjFiGVuwqFQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0", @@ -15987,9 +15991,9 @@ } }, "node_modules/postcss-unique-selectors": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.2.tgz", - "integrity": "sha512-8IZGQ94nechdG7Y9Sh9FlIY2b4uS8/k8kdKRX040XHsS3B6d1HrJAkXrBSsSu4SuARruSsUjW3nlSw8BHkaAYQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.3.tgz", + "integrity": "sha512-NFXbYr8qdmCr/AFceaEfdcsKGCvWTeGO6QVC9h2GvtWgj0/0dklKQcaMMVzs6tr8bY+ase8hOtHW8OBTTRvS8A==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.15" @@ -18225,12 +18229,12 @@ "dev": true }, "node_modules/stylehacks": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.2.tgz", - "integrity": "sha512-00zvJGnCu64EpMjX8b5iCZ3us2Ptyw8+toEkb92VdmkEaRaSGBNKAoK6aWZckhXxmQP8zWiTaFaiMGIU8Ve8sg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.0.tgz", + "integrity": "sha512-ETErsPFgwlfYZ/CSjMO2Ddf+TsnkCVPBPaoB99Ro8WMAxf7cglzmFsRBhRmKObFjibtcvlNxFFPHuyr3sNlNUQ==", "dev": true, "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.0", "postcss-selector-parser": "^6.0.15" }, "engines": { @@ -27262,13 +27266,13 @@ } }, "browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -27412,9 +27416,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001579", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", - "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==", + "version": "1.0.30001596", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001596.tgz", + "integrity": "sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==", "dev": true }, "canvas": { @@ -28060,56 +28064,57 @@ "dev": true }, "cssnano": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.3.tgz", - "integrity": "sha512-MRq4CIj8pnyZpcI2qs6wswoYoDD1t0aL28n+41c1Ukcpm56m1h6mCexIHBGjfZfnTqtGSSCP4/fB1ovxgjBOiw==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.5.tgz", + "integrity": "sha512-tpTp/ukgrElwu3ESFY4IvWnGn8eTt8cJhC2aAbtA3lvUlxp6t6UPv8YCLjNnEGiFreT1O0LiOM1U3QyTBVFl2A==", "dev": true, "requires": { - "cssnano-preset-default": "^6.0.3", - "lilconfig": "^3.0.0" + "cssnano-preset-default": "^6.0.5", + "lilconfig": "^3.1.1" } }, "cssnano-preset-default": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.3.tgz", - "integrity": "sha512-4y3H370aZCkT9Ev8P4SO4bZbt+AExeKhh8wTbms/X7OLDo5E7AYUUy6YPxa/uF5Grf+AJwNcCnxKhZynJ6luBA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.0.tgz", + "integrity": "sha512-4DUXZoDj+PI3fRl3MqMjl9DwLGjcsFP4qt+92nLUcN1RGfw2TY+GwNoG2B38Usu1BrcTs8j9pxNfSusmvtSjfg==", "dev": true, "requires": { + "browserslist": "^4.23.0", "css-declaration-sorter": "^7.1.1", - "cssnano-utils": "^4.0.1", + "cssnano-utils": "^4.0.2", "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.0.2", - "postcss-convert-values": "^6.0.2", - "postcss-discard-comments": "^6.0.1", - "postcss-discard-duplicates": "^6.0.1", - "postcss-discard-empty": "^6.0.1", - "postcss-discard-overridden": "^6.0.1", - "postcss-merge-longhand": "^6.0.2", - "postcss-merge-rules": "^6.0.3", - "postcss-minify-font-values": "^6.0.1", - "postcss-minify-gradients": "^6.0.1", - "postcss-minify-params": "^6.0.2", - "postcss-minify-selectors": "^6.0.2", - "postcss-normalize-charset": "^6.0.1", - "postcss-normalize-display-values": "^6.0.1", - "postcss-normalize-positions": "^6.0.1", - "postcss-normalize-repeat-style": "^6.0.1", - "postcss-normalize-string": "^6.0.1", - "postcss-normalize-timing-functions": "^6.0.1", - "postcss-normalize-unicode": "^6.0.2", - "postcss-normalize-url": "^6.0.1", - "postcss-normalize-whitespace": "^6.0.1", - "postcss-ordered-values": "^6.0.1", - "postcss-reduce-initial": "^6.0.2", - "postcss-reduce-transforms": "^6.0.1", - "postcss-svgo": "^6.0.2", - "postcss-unique-selectors": "^6.0.2" + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.4", + "postcss-merge-rules": "^6.1.0", + "postcss-minify-font-values": "^6.0.3", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.3", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.3" } }, "cssnano-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.1.tgz", - "integrity": "sha512-6qQuYDqsGoiXssZ3zct6dcMxiqfT6epy7x4R0TQJadd4LWO3sPR6JH6ZByOvVLoZ6EdwPGgd7+DR1EmX3tiXQQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", "dev": true, "requires": {} }, @@ -28547,9 +28552,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.4.623", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.623.tgz", - "integrity": "sha512-lKoz10iCYlP1WtRYdh5MvocQPWVRoI7ysp6qf18bmeBgR8abE6+I2CsfyNKztRDZvhdWc+krKT6wS7Neg8sw3A==", + "version": "1.4.697", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.697.tgz", + "integrity": "sha512-iPS+iUNUrqTkPRFjMYv1FGXIUYhj2K4rc/93nrDsDtQGMUqyRouCq/xABOSOljKbriEiwg0bEQHGaeD4OaU56g==", "dev": true }, "emoji-regex": { @@ -31889,9 +31894,9 @@ } }, "lilconfig": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", - "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", "dev": true }, "lines-and-columns": { @@ -33213,24 +33218,24 @@ } }, "postcss-colormin": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.2.tgz", - "integrity": "sha512-TXKOxs9LWcdYo5cgmcSHPkyrLAh86hX1ijmyy6J8SbOhyv6ua053M3ZAM/0j44UsnQNIWdl8gb5L7xX2htKeLw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.0", "caniuse-api": "^3.0.0", - "colord": "^2.9.1", + "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" } }, "postcss-convert-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.2.tgz", - "integrity": "sha512-aeBmaTnGQ+NUSVQT8aY0sKyAD/BaLJenEKZ03YK0JnDE1w1Rr8XShoxdal2V2H26xTJKr3v5haByOhJuyT4UYw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" } }, @@ -33280,30 +33285,30 @@ } }, "postcss-discard-comments": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.1.tgz", - "integrity": "sha512-f1KYNPtqYLUeZGCHQPKzzFtsHaRuECe6jLakf/RjSRqvF5XHLZnM2+fXLhb8Qh/HBFHs3M4cSLb1k3B899RYIg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", "dev": true, "requires": {} }, "postcss-discard-duplicates": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.1.tgz", - "integrity": "sha512-1hvUs76HLYR8zkScbwyJ8oJEugfPV+WchpnA+26fpJ7Smzs51CzGBHC32RS03psuX/2l0l0UKh2StzNxOrKCYg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", "dev": true, "requires": {} }, "postcss-discard-empty": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.1.tgz", - "integrity": "sha512-yitcmKwmVWtNsrrRqGJ7/C0YRy53i0mjexBDQ9zYxDwTWVBgbU4+C9jIZLmQlTDT9zhml+u0OMFJh8+31krmOg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", "dev": true, "requires": {} }, "postcss-discard-overridden": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.1.tgz", - "integrity": "sha512-qs0ehZMMZpSESbRkw1+inkf51kak6OOzNRaoLd/U7Fatp0aN2HQ1rxGOrJvYcRAN9VpX8kUF13R2ofn8OlvFVA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", "dev": true, "requires": {} }, @@ -33700,62 +33705,62 @@ "dev": true }, "postcss-merge-longhand": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.2.tgz", - "integrity": "sha512-+yfVB7gEM8SrCo9w2lCApKIEzrTKl5yS1F4yGhV3kSim6JzbfLGJyhR1B6X+6vOT0U33Mgx7iv4X9MVWuaSAfw==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.4.tgz", + "integrity": "sha512-vAfWGcxUUGlFiPM3nDMZA+/Yo9sbpc3JNkcYZez8FfJDv41Dh7tAgA3QGVTocaHCZZL6aXPXPOaBMJsjujodsA==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.0.2" + "stylehacks": "^6.1.0" } }, "postcss-merge-rules": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.3.tgz", - "integrity": "sha512-yfkDqSHGohy8sGYIJwBmIGDv4K4/WrJPX355XrxQb/CSsT4Kc/RxDi6akqn5s9bap85AWgv21ArcUWwWdGNSHA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.0.tgz", + "integrity": "sha512-lER+W3Gr6XOvxOYk1Vi/6UsAgKMg6MDBthmvbNqi2XxAk/r9XfhdYZSigfWjuWWn3zYw2wLelvtM8XuAEFqRkA==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.0", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.1", + "cssnano-utils": "^4.0.2", "postcss-selector-parser": "^6.0.15" } }, "postcss-minify-font-values": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.1.tgz", - "integrity": "sha512-tIwmF1zUPoN6xOtA/2FgVk1ZKrLcCvE0dpZLtzyyte0j9zUeB8RTbCqrHZGjJlxOvNWKMYtunLrrl7HPOiR46w==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.3.tgz", + "integrity": "sha512-SmAeTA1We5rMnN3F8X9YBNo9bj9xB4KyDHnaNJnBfQIPi+60fNiR9OTRnIaMqkYzAQX0vObIw4Pn0vuKEOettg==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-minify-gradients": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.1.tgz", - "integrity": "sha512-M1RJWVjd6IOLPl1hYiOd5HQHgpp6cvJVLrieQYS9y07Yo8itAr6jaekzJphaJFR0tcg4kRewCk3kna9uHBxn/w==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", "dev": true, "requires": { - "colord": "^2.9.1", - "cssnano-utils": "^4.0.1", + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" } }, "postcss-minify-params": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.2.tgz", - "integrity": "sha512-zwQtbrPEBDj+ApELZ6QylLf2/c5zmASoOuA4DzolyVGdV38iR2I5QRMsZcHkcdkZzxpN8RS4cN7LPskOkTwTZw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", "dev": true, "requires": { - "browserslist": "^4.22.2", - "cssnano-utils": "^4.0.1", + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" } }, "postcss-minify-selectors": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.2.tgz", - "integrity": "sha512-0b+m+w7OAvZejPQdN2GjsXLv5o0jqYHX3aoV0e7RBKPCsB7TYG5KKWBFhGnB/iP3213Ts8c5H4wLPLMm7z28Sg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.3.tgz", + "integrity": "sha512-IcV7ZQJcaXyhx4UBpWZMsinGs2NmiUC60rJSkyvjPCPqhNjVGsrJUM+QhAtCaikZ0w0/AbZuH4wVvF/YMuMhvA==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.15" @@ -33808,80 +33813,80 @@ } }, "postcss-normalize-charset": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.1.tgz", - "integrity": "sha512-aW5LbMNRZ+oDV57PF9K+WI1Z8MPnF+A8qbajg/T8PP126YrGX1f9IQx21GI2OlGz7XFJi/fNi0GTbY948XJtXg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", "dev": true, "requires": {} }, "postcss-normalize-display-values": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.1.tgz", - "integrity": "sha512-mc3vxp2bEuCb4LgCcmG1y6lKJu1Co8T+rKHrcbShJwUmKJiEl761qb/QQCfFwlrvSeET3jksolCR/RZuMURudw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-positions": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.1.tgz", - "integrity": "sha512-HRsq8u/0unKNvm0cvwxcOUEcakFXqZ41fv3FOdPn916XFUrympjr+03oaLkuZENz3HE9RrQE9yU0Xv43ThWjQg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-repeat-style": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.1.tgz", - "integrity": "sha512-Gbb2nmCy6tTiA7Sh2MBs3fj9W8swonk6lw+dFFeQT68B0Pzwp1kvisJQkdV6rbbMSd9brMlS8I8ts52tAGWmGQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-string": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.1.tgz", - "integrity": "sha512-5Fhx/+xzALJD9EI26Aq23hXwmv97Zfy2VFrt5PLT8lAhnBIZvmaT5pQk+NuJ/GWj/QWaKSKbnoKDGLbV6qnhXg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-timing-functions": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.1.tgz", - "integrity": "sha512-4zcczzHqmCU7L5dqTB9rzeqPWRMc0K2HoR+Bfl+FSMbqGBUcP5LRfgcH4BdRtLuzVQK1/FHdFoGT3F7rkEnY+g==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-unicode": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.2.tgz", - "integrity": "sha512-Ff2VdAYCTGyMUwpevTZPZ4w0+mPjbZzLLyoLh/RMpqUqeQKZ+xMm31hkxBavDcGKcxm6ACzGk0nBfZ8LZkStKA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.1.tgz", - "integrity": "sha512-jEXL15tXSvbjm0yzUV7FBiEXwhIa9H88JOXDGQzmcWoB4mSjZIsmtto066s2iW9FYuIrIF4k04HA2BKAOpbsaQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-whitespace": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.1.tgz", - "integrity": "sha512-76i3NpWf6bB8UHlVuLRxG4zW2YykF9CTEcq/9LGAiz2qBuX5cBStadkk0jSkg9a9TCIXbMQz7yzrygKoCW9JuA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" @@ -33895,12 +33900,12 @@ "requires": {} }, "postcss-ordered-values": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.1.tgz", - "integrity": "sha512-XXbb1O/MW9HdEhnBxitZpPFbIvDgbo9NK4c/5bOfiKpnIGZDoL2xd7/e6jW5DYLsWxBbs+1nZEnVgnjnlFViaA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", "dev": true, "requires": { - "cssnano-utils": "^4.0.1", + "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" } }, @@ -34007,19 +34012,19 @@ } }, "postcss-reduce-initial": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.2.tgz", - "integrity": "sha512-YGKalhNlCLcjcLvjU5nF8FyeCTkCO5UtvJEt0hrPZVCTtRLSOH4z00T1UntQPj4dUmIYZgMj8qK77JbSX95hSw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.0", "caniuse-api": "^3.0.0" } }, "postcss-reduce-transforms": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.1.tgz", - "integrity": "sha512-fUbV81OkUe75JM+VYO1gr/IoA2b/dRiH6HvMwhrIBSUrxq3jNZQZitSnugcTLDi1KkQh1eR/zi+iyxviUNBkcQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" @@ -34189,9 +34194,9 @@ } }, "postcss-svgo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.2.tgz", - "integrity": "sha512-IH5R9SjkTkh0kfFOQDImyy1+mTCb+E830+9SV1O+AaDcoHTvfsvt6WwJeo7KwcHbFnevZVCsXhDmjFiGVuwqFQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0", @@ -34206,9 +34211,9 @@ "requires": {} }, "postcss-unique-selectors": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.2.tgz", - "integrity": "sha512-8IZGQ94nechdG7Y9Sh9FlIY2b4uS8/k8kdKRX040XHsS3B6d1HrJAkXrBSsSu4SuARruSsUjW3nlSw8BHkaAYQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.3.tgz", + "integrity": "sha512-NFXbYr8qdmCr/AFceaEfdcsKGCvWTeGO6QVC9h2GvtWgj0/0dklKQcaMMVzs6tr8bY+ase8hOtHW8OBTTRvS8A==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.15" @@ -35916,12 +35921,12 @@ "dev": true }, "stylehacks": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.2.tgz", - "integrity": "sha512-00zvJGnCu64EpMjX8b5iCZ3us2Ptyw8+toEkb92VdmkEaRaSGBNKAoK6aWZckhXxmQP8zWiTaFaiMGIU8Ve8sg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.0.tgz", + "integrity": "sha512-ETErsPFgwlfYZ/CSjMO2Ddf+TsnkCVPBPaoB99Ro8WMAxf7cglzmFsRBhRmKObFjibtcvlNxFFPHuyr3sNlNUQ==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.0", "postcss-selector-parser": "^6.0.15" } }, diff --git a/package.json b/package.json index 6b68ce0001..b7a307ec1d 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "6.9.1", - "cssnano": "6.0.3", + "cssnano": "6.0.5", "es-check": "7.1.1", "eslint": "8.56.0", "eslint-plugin-compat": "4.2.0", From 51edf041d37467a6bfac3d71a36e7c2dc30f4430 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Thu, 7 Mar 2024 23:17:34 -0500 Subject: [PATCH 84/90] Remove Azure pipelines CI Obsoleted by jellyfin-packaging and GitHub Actions CI. Goodnight sweet prince. --- .ci/azure-pipelines-build.yml | 55 -------------- .ci/azure-pipelines-package.yml | 126 -------------------------------- .ci/azure-pipelines.yml | 16 ---- 3 files changed, 197 deletions(-) delete mode 100644 .ci/azure-pipelines-build.yml delete mode 100644 .ci/azure-pipelines-package.yml delete mode 100644 .ci/azure-pipelines.yml diff --git a/.ci/azure-pipelines-build.yml b/.ci/azure-pipelines-build.yml deleted file mode 100644 index d873172396..0000000000 --- a/.ci/azure-pipelines-build.yml +++ /dev/null @@ -1,55 +0,0 @@ -jobs: -- job: Build - displayName: 'Build' - - strategy: - matrix: - Development: - BuildConfiguration: development - Production: - BuildConfiguration: production - - pool: - vmImage: 'ubuntu-latest' - - steps: - - task: NodeTool@0 - displayName: 'Install Node' - inputs: - versionSpec: '20.x' - - - task: Cache@2 - displayName: 'Cache node_modules' - inputs: - key: 'npm | package-lock.json' - path: 'node_modules' - - - script: 'npm ci --no-audit' - displayName: 'Install Dependencies' - - - script: 'npm run build:development' - displayName: 'Build Development' - condition: eq(variables['BuildConfiguration'], 'development') - - - script: 'npm run build:production' - displayName: 'Build Production' - condition: eq(variables['BuildConfiguration'], 'production') - - - script: 'test -d dist' - displayName: 'Check Build' - - - script: 'mv dist jellyfin-web' - displayName: 'Rename Directory' - - - task: ArchiveFiles@2 - displayName: 'Archive Directory' - inputs: - rootFolderOrFile: 'jellyfin-web' - includeRootFolder: true - archiveFile: 'jellyfin-web-$(BuildConfiguration)' - - - task: PublishPipelineArtifact@1 - displayName: 'Publish Release' - inputs: - targetPath: '$(Build.SourcesDirectory)/jellyfin-web-$(BuildConfiguration).zip' - artifactName: 'jellyfin-web-$(BuildConfiguration)' diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml deleted file mode 100644 index 0081fbb907..0000000000 --- a/.ci/azure-pipelines-package.yml +++ /dev/null @@ -1,126 +0,0 @@ -jobs: -- job: BuildPackage - displayName: 'Build Packages' - - strategy: - matrix: - CentOS: - BuildConfiguration: centos - Debian: - BuildConfiguration: debian - Fedora: - BuildConfiguration: fedora - Portable: - BuildConfiguration: portable - - pool: - vmImage: 'ubuntu-latest' - - steps: - - script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )" - displayName: Set release version (stable) - condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') - - - script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-web-$(BuildConfiguration) deployment' - displayName: 'Build Dockerfile' - condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) - - - script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="yes" -e BUILD_ID=$(Build.BuildNumber) jellyfin-web-$(BuildConfiguration)' - displayName: 'Run Dockerfile (unstable)' - condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') - - - script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="no" -e BUILD_ID=$(Build.BuildNumber) jellyfin-web-$(BuildConfiguration)' - displayName: 'Run Dockerfile (stable)' - condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') - - - task: PublishPipelineArtifact@1 - displayName: 'Publish Release' - condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) - inputs: - targetPath: '$(Build.SourcesDirectory)/deployment/dist' - artifactName: 'jellyfin-web-$(BuildConfiguration)' - - - task: SSH@0 - displayName: 'Create target directory on repository server' - condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) - inputs: - sshEndpoint: repository - runOptions: 'inline' - inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)' - - - task: CopyFilesOverSSH@0 - displayName: 'Upload artifacts to repository server' - condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) - inputs: - sshEndpoint: repository - sourceFolder: '$(Build.SourcesDirectory)/deployment/dist' - contents: '**' - targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)' - -- job: BuildDocker - displayName: 'Build Docker' - - pool: - vmImage: 'ubuntu-latest' - - variables: - - name: JellyfinVersion - value: 0.0.0 - - steps: - - script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )" - displayName: Set release version (stable) - condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') - - - task: Docker@2 - displayName: 'Push Unstable Image' - condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') - inputs: - repository: 'jellyfin/jellyfin-web' - command: buildAndPush - buildContext: '.' - Dockerfile: 'deployment/Dockerfile.docker' - containerRegistry: Docker Hub - tags: | - unstable-$(Build.BuildNumber) - unstable - - - task: Docker@2 - displayName: 'Push Stable Image' - condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') - inputs: - repository: 'jellyfin/jellyfin-web' - command: buildAndPush - buildContext: '.' - Dockerfile: 'deployment/Dockerfile.docker' - containerRegistry: Docker Hub - tags: | - stable-$(Build.BuildNumber) - $(JellyfinVersion) - -- job: CollectArtifacts - displayName: 'Collect Artifacts' - dependsOn: - - BuildPackage - - BuildDocker - condition: and(succeeded('BuildPackage'), succeeded('BuildDocker')) - - pool: - vmImage: 'ubuntu-latest' - - steps: - - task: SSH@0 - displayName: 'Update Unstable Repository' - condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') - inputs: - sshEndpoint: repository - runOptions: 'inline' - inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable' - - - task: SSH@0 - displayName: 'Update Stable Repository' - condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') - inputs: - sshEndpoint: repository - runOptions: 'inline' - inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) $(Build.SourceBranch)' diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml deleted file mode 100644 index d3b77d41bc..0000000000 --- a/.ci/azure-pipelines.yml +++ /dev/null @@ -1,16 +0,0 @@ -trigger: - batch: true - branches: - include: - - '*' - tags: - include: - - '*' -pr: - branches: - include: - - '*' - -jobs: -- template: azure-pipelines-build.yml -- template: azure-pipelines-package.yml From 114953f8ae7b39d8d62e21731905ad391b6acbf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Fri, 8 Mar 2024 07:07:59 +0000 Subject: [PATCH 85/90] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/cs/ --- src/strings/cs.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/strings/cs.json b/src/strings/cs.json index e5f0df0e76..f7f6bb1bc9 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -1786,5 +1786,11 @@ "LabelBuildVersion": "Verze sestavení", "LabelServerVersion": "Verze serveru", "LabelWebVersion": "Verze webu", - "ButtonEditUser": "Upravit uživatele" + "ButtonEditUser": "Upravit uživatele", + "DlnaMovedMessage": "Funkce DLNA byla přesunuta do zásuvného modulu.", + "ChannelResolutionSD": "SD", + "ChannelResolutionSDPAL": "SD (PAL)", + "ChannelResolutionHD": "HD", + "ChannelResolutionFullHD": "Full HD", + "ChannelResolutionUHD4K": "UHD (4K)" } From 0d01cf6bda6f3faaf5b2ef0f7b2ffc9c6a811c65 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Fri, 8 Mar 2024 09:43:35 -0500 Subject: [PATCH 86/90] Revert "Bump es5-ext from 0.10.53 to 0.10.64" This reverts commit 3994486bf432a0efcab2a90f6035a3ce38be94cb. --- package-lock.json | 79 +++++++++++------------------------------------ 1 file changed, 18 insertions(+), 61 deletions(-) diff --git a/package-lock.json b/package-lock.json index 33b338c2be..767a198c1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8401,18 +8401,13 @@ } }, "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "hasInstallScript": true, + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" } }, "node_modules/es6-iterator": { @@ -9182,25 +9177,6 @@ "node": ">=8" } }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esniff/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -13441,9 +13417,9 @@ "dev": true }, "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, "node_modules/no-case": { "version": "3.0.4", @@ -28770,14 +28746,13 @@ } }, "es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" } }, "es6-iterator": { @@ -29349,24 +29324,6 @@ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true }, - "esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "requires": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "dependencies": { - "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - } - } - }, "espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -32525,9 +32482,9 @@ "dev": true }, "next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, "no-case": { "version": "3.0.4", From 2e6421fce965dd2c9c047b2e4c7c3037fab360f1 Mon Sep 17 00:00:00 2001 From: stanol Date: Fri, 8 Mar 2024 14:05:07 +0000 Subject: [PATCH 87/90] Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/uk/ --- src/strings/uk.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/strings/uk.json b/src/strings/uk.json index 98b18ecf63..c3f33203cc 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -1784,5 +1784,10 @@ "LabelServerVersion": "Версія сервера", "LabelWebVersion": "Версія вебу", "ButtonEditUser": "Редагувати користувача", - "DlnaMovedMessage": "Функціональність DLNA перенесено у плагін." + "DlnaMovedMessage": "Функціональність DLNA перенесено у плагін.", + "ChannelResolutionSD": "SD", + "ChannelResolutionSDPAL": "SD (PAL)", + "ChannelResolutionHD": "HD", + "ChannelResolutionFullHD": "Full HD", + "ChannelResolutionUHD4K": "UHD (4K)" } From 6125a8cccb2a25586698fd6967b862c5572504a4 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Fri, 8 Mar 2024 11:28:46 -0500 Subject: [PATCH 88/90] Update editorconfig for yaml indentation --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 84ba694073..9fb3eefd1a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,5 +8,5 @@ trim_trailing_whitespace = true insert_final_newline = true end_of_line = lf -[*.json] +[*.{json,yaml,yml}] indent_size = 2 From d69569926cee0c6b2ba969d0ef011124b0e7b8c1 Mon Sep 17 00:00:00 2001 From: Miroslav Ivanov Date: Fri, 8 Mar 2024 18:11:43 +0000 Subject: [PATCH 89/90] Translated using Weblate (Bulgarian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/bg/ --- src/strings/bg-bg.json | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/strings/bg-bg.json b/src/strings/bg-bg.json index 7a7b75de42..5329f5bb02 100644 --- a/src/strings/bg-bg.json +++ b/src/strings/bg-bg.json @@ -231,7 +231,7 @@ "LabelCollection": "Поредица", "LabelCommunityRating": "Обществена оценка", "LabelContentType": "Тип на съдържанието", - "LabelCountry": "Държава", + "LabelCountry": "Държава/Регион", "LabelCriticRating": "Оценка на критиците", "LabelCurrentPassword": "Текуща парола", "LabelCustomCertificatePath": "Ръчно задаване на пътя към SSL сертификата", @@ -1464,7 +1464,7 @@ "LabelMaxDaysForNextUpHelp": "Задайте максималния брой дни, през които едно шоу би трябвало да остане в списъка „Next Up“, без да го гледате.", "LabelEnableAudioVbr": "Разреши VBR звуково кодиране", "HeaderPerformance": "Производителност", - "LabelChapterImageResolutionHelp": "Резолюцията на извлечените снимки към раздела.", + "LabelChapterImageResolutionHelp": "Резолюцията на извлечените снимки към раздела. Промяна в тези настройки няма да повлияе на съществуващите тестови раздели.", "AllowCollectionManagement": "Позволи този потребител да управлява колекции", "LabelDummyChapterDuration": "Интервал", "LabelDummyChapterCount": "Граница", @@ -1488,5 +1488,19 @@ "LabelDeveloper": "Програмист", "LabelDate": "Дата", "GridView": "Грид изглед", - "HeaderRecordingMetadataSaving": "Запис на Мета-данни" + "HeaderRecordingMetadataSaving": "Запис на Мета-данни", + "ButtonEditUser": "Редактиране на потребител", + "ChannelResolutionSD": "SD", + "ChannelResolutionHD": "Висока резолюция", + "ChannelResolutionFullHD": "Пълна висока резолюция", + "ChannelResolutionUHD4K": "4К Резолюция", + "HeaderDummyChapter": "Кадъри от видео сегмент", + "HeaderEpisodesStatus": "Статус на епизод", + "LabelMediaDetails": "Детайли на медия", + "HeaderAllRecordings": "Всички записи", + "LabelLevel": "Ниво", + "LabelSelectAudioNormalization": "Аудио нормализация", + "LabelBuildVersion": "Софтуерна версия", + "LabelEnableLUFSScanHelp": "Нормализаране на силата на звука на всички аудио файлове. Това ще увеличи времети за сканиране на библиотеката и ще използва допъкнителни ресурси.", + "HeaderGuestCast": "Гостуващи актьори" } From c8ccd2dde31233e2c6d9883607bdd10804f7d8e1 Mon Sep 17 00:00:00 2001 From: Blackspirits Date: Sat, 9 Mar 2024 02:21:16 +0000 Subject: [PATCH 90/90] Translated using Weblate (Portuguese (Portugal)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt_PT/ --- src/strings/pt-pt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/pt-pt.json b/src/strings/pt-pt.json index 4376ca3641..d4e5a3883b 100644 --- a/src/strings/pt-pt.json +++ b/src/strings/pt-pt.json @@ -1675,7 +1675,7 @@ "Interview": "Entrevista", "DeletedScene": "Cenas removidas", "BehindTheScenes": "Nos bastidores", - "DownloadAll": "Transferir Todas", + "DownloadAll": "Transferir todas", "MessageNoItemsAvailable": "Nenhum item disponível atualmente.", "MessageNoFavoritesAvailable": "Nenhum favorito disponível atualmente.", "SubtitleBlack": "Preto",