mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into trickplay-new
This commit is contained in:
commit
2dfc0aa061
137 changed files with 5909 additions and 2829 deletions
92
src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx
Normal file
92
src/elements/emby-itemrefreshindicator/RefreshIndicator.tsx
Normal file
|
@ -0,0 +1,92 @@
|
|||
import React, { type 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/base/models/item-dto';
|
||||
|
||||
function CircularProgressWithLabel(
|
||||
props: CircularProgressProps & { value: number }
|
||||
) {
|
||||
return (
|
||||
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
|
||||
<CircularProgress variant='determinate' {...props} />
|
||||
<Box
|
||||
sx={{
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
position: 'absolute',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant='caption'
|
||||
component='div'
|
||||
color='text.secondary'
|
||||
>
|
||||
{toPercent(props.value / 100, getCurrentDateTimeLocale())}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
interface RefreshIndicatorProps {
|
||||
item: ItemDto;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const RefreshIndicator: FC<RefreshIndicatorProps> = ({ 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 (
|
||||
<div className={progressringClass}>
|
||||
<CircularProgressWithLabel value={Math.floor(progress)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RefreshIndicator;
|
|
@ -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 React, { type 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';
|
||||
|
@ -21,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();
|
||||
|
@ -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<ItemsContainerProps> = ({
|
||||
|
@ -52,12 +50,14 @@ const ItemsContainer: FC<ItemsContainerProps> = ({
|
|||
isContextMenuEnabled,
|
||||
isMultiSelectEnabled,
|
||||
isDragreOrderEnabled,
|
||||
dataMonitor,
|
||||
eventsToMonitor = [],
|
||||
parentId,
|
||||
queryKey,
|
||||
reloadItems,
|
||||
getItemsHtml,
|
||||
children
|
||||
}) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutateAsync: playlistsMoveItemMutation } = usePlaylistsMoveItemMutation();
|
||||
const itemsContainerRef = useRef<HTMLDivElement>(null);
|
||||
const multiSelectref = useRef<MultiSelect | null>(null);
|
||||
|
@ -172,6 +172,14 @@ const ItemsContainer: FC<ItemsContainerProps> = ({
|
|||
}
|
||||
}, []);
|
||||
|
||||
const invalidateQueries = useCallback(async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey,
|
||||
type: 'all',
|
||||
refetchType: 'active'
|
||||
});
|
||||
}, [queryClient, queryKey]);
|
||||
|
||||
const notifyRefreshNeeded = useCallback(
|
||||
(isInForeground: boolean) => {
|
||||
if (!reloadItems) return;
|
||||
|
@ -184,144 +192,37 @@ const ItemsContainer: FC<ItemsContainerProps> = ({
|
|||
[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<ItemsContainerProps> = ({
|
|||
|
||||
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<ItemsContainerProps> = ({
|
|||
|
||||
if (getItemsHtml) {
|
||||
itemsContainer.innerHTML = getItemsHtml();
|
||||
imageLoader.lazyChildren(itemsContainer);
|
||||
}
|
||||
|
||||
imageLoader.lazyChildren(itemsContainer);
|
||||
|
||||
if (hasActiveElement) {
|
||||
setFocus(itemsContainer, focusId);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
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';
|
||||
import { IconButton } from '@mui/material';
|
||||
import classNames from 'classnames';
|
||||
|
@ -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<PlayedButtonProps> = ({
|
||||
className,
|
||||
isPlayed = false,
|
||||
itemId,
|
||||
itemType
|
||||
itemType,
|
||||
queryKey
|
||||
}) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutateAsync: togglePlayedMutation } = useTogglePlayedMutation();
|
||||
const [playedState, setPlayedState] = React.useState<boolean>(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<PlayedButtonProps> = ({
|
|||
throw new Error('Item has no Id');
|
||||
}
|
||||
|
||||
const _isPlayed = await togglePlayedMutation({
|
||||
await togglePlayedMutation({
|
||||
itemId,
|
||||
playedState
|
||||
});
|
||||
setPlayedState(!!_isPlayed);
|
||||
isPlayed
|
||||
},
|
||||
{ onSuccess: async() => {
|
||||
await queryClient.invalidateQueries({
|
||||
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 (
|
||||
<IconButton
|
||||
|
|
79
src/elements/emby-progressbar/AutoTimeProgressBar.tsx
Normal file
79
src/elements/emby-progressbar/AutoTimeProgressBar.tsx
Normal file
|
@ -0,0 +1,79 @@
|
|||
import React, { type FC, useCallback, useEffect, useRef, useState } from 'react';
|
||||
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 {
|
||||
pct: number;
|
||||
starTtime: number;
|
||||
endTtime: number;
|
||||
isRecording: boolean;
|
||||
dataAutoMode?: string;
|
||||
progressOptions?: ProgressOptions;
|
||||
}
|
||||
|
||||
const AutoTimeProgressBar: FC<AutoTimeProgressBarProps> = ({
|
||||
pct,
|
||||
dataAutoMode,
|
||||
isRecording,
|
||||
starTtime,
|
||||
endTtime,
|
||||
progressOptions
|
||||
}) => {
|
||||
const [progress, setProgress] = useState(pct);
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const theme = useTheme();
|
||||
|
||||
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 (
|
||||
<LinearProgress
|
||||
className={progressBarClass}
|
||||
variant='determinate'
|
||||
value={progress}
|
||||
sx={{
|
||||
[`& .${linearProgressClasses.bar}`]: {
|
||||
borderRadius: 5,
|
||||
backgroundColor: isRecording ? theme.palette.error.main : theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutoTimeProgressBar;
|
|
@ -1,4 +1,5 @@
|
|||
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';
|
||||
import classNames from 'classnames';
|
||||
|
@ -8,16 +9,18 @@ import globalize from 'scripts/globalize';
|
|||
interface FavoriteButtonProps {
|
||||
className?: string;
|
||||
isFavorite: boolean | undefined;
|
||||
itemId: string | null | undefined
|
||||
itemId: string | null | undefined;
|
||||
queryKey?: string[]
|
||||
}
|
||||
|
||||
const FavoriteButton: FC<FavoriteButtonProps> = ({
|
||||
className,
|
||||
isFavorite = false,
|
||||
itemId
|
||||
itemId,
|
||||
queryKey
|
||||
}) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutateAsync: toggleFavoriteMutation } = useToggleFavoriteMutation();
|
||||
const [favoriteState, setFavoriteState] = React.useState<boolean>(isFavorite);
|
||||
|
||||
const onClick = useCallback(async () => {
|
||||
try {
|
||||
|
@ -25,28 +28,34 @@ const FavoriteButton: FC<FavoriteButtonProps> = ({
|
|||
throw new Error('Item has no Id');
|
||||
}
|
||||
|
||||
const _isFavorite = await toggleFavoriteMutation({
|
||||
await toggleFavoriteMutation({
|
||||
itemId,
|
||||
favoriteState
|
||||
});
|
||||
setFavoriteState(!!_isFavorite);
|
||||
isFavorite
|
||||
},
|
||||
{ onSuccess: async() => {
|
||||
await queryClient.invalidateQueries({
|
||||
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 (
|
||||
<IconButton
|
||||
title={favoriteState ? globalize.translate('Favorite') : globalize.translate('AddToFavorites')}
|
||||
title={isFavorite ? globalize.translate('Favorite') : globalize.translate('AddToFavorites')}
|
||||
className={btnClass}
|
||||
size='small'
|
||||
onClick={onClick}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue