mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 23:59:08 +00:00
refactor: playbutton for tv
This commit is contained in:
@@ -13,7 +13,10 @@ import {
|
||||
ProgressUpdatePayload,
|
||||
VlcPlayerViewRef,
|
||||
} from "@/modules/vlc-player/src/VlcPlayer.types";
|
||||
import { useDownload } from "@/providers/DownloadProvider";
|
||||
// import { useDownload } from "@/providers/DownloadProvider";
|
||||
const useDownload = !Platform.isTV
|
||||
? require("@/providers/DownloadProvider")
|
||||
: null;
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
||||
import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
|
||||
@@ -68,7 +71,10 @@ export default function page() {
|
||||
const isSeeking = useSharedValue(false);
|
||||
const cacheProgress = useSharedValue(0);
|
||||
|
||||
const { getDownloadedItem } = useDownload();
|
||||
if (!Platform.isTV) {
|
||||
const { getDownloadedItem } = useDownload();
|
||||
}
|
||||
|
||||
const revalidateProgressCache = useInvalidatePlaybackProgressCache();
|
||||
|
||||
const lightHapticFeedback = useHaptic("light");
|
||||
@@ -109,7 +115,7 @@ export default function page() {
|
||||
} = useQuery({
|
||||
queryKey: ["item", itemId],
|
||||
queryFn: async () => {
|
||||
if (offline) {
|
||||
if (offline && !Platform.isTV) {
|
||||
const item = await getDownloadedItem(itemId);
|
||||
if (item) return item.item;
|
||||
}
|
||||
@@ -132,7 +138,7 @@ export default function page() {
|
||||
} = useQuery({
|
||||
queryKey: ["stream-url", itemId, mediaSourceId, bitrateValue],
|
||||
queryFn: async () => {
|
||||
if (offline) {
|
||||
if (offline && !Platform.isTV) {
|
||||
const data = await getDownloadedItem(itemId);
|
||||
if (!data?.mediaSource) return null;
|
||||
|
||||
|
||||
@@ -252,13 +252,13 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo(
|
||||
</View>
|
||||
)}
|
||||
|
||||
{!Platform.isTV && (
|
||||
<PlayButton
|
||||
className="grow"
|
||||
selectedOptions={selectedOptions}
|
||||
item={item}
|
||||
/>
|
||||
)}
|
||||
{/* {!Platform.isTV && ( */}
|
||||
<PlayButton
|
||||
className="grow"
|
||||
selectedOptions={selectedOptions}
|
||||
item={item}
|
||||
/>
|
||||
{/* )} */}
|
||||
</View>
|
||||
|
||||
{item.Type === "Episode" && (
|
||||
|
||||
@@ -117,99 +117,101 @@ export const PlayButton: React.FC<Props> = ({
|
||||
|
||||
switch (selectedIndex) {
|
||||
case 0:
|
||||
await CastContext.getPlayServicesState().then(async (state) => {
|
||||
if (state && state !== PlayServicesState.SUCCESS)
|
||||
CastContext.showPlayServicesErrorDialog(state);
|
||||
else {
|
||||
// Get a new URL with the Chromecast device profile:
|
||||
const data = await getStreamUrl({
|
||||
api,
|
||||
item,
|
||||
deviceProfile: chromecastProfile,
|
||||
startTimeTicks: item?.UserData?.PlaybackPositionTicks!,
|
||||
userId: user?.Id,
|
||||
audioStreamIndex: selectedOptions.audioIndex,
|
||||
maxStreamingBitrate: selectedOptions.bitrate?.value,
|
||||
mediaSourceId: selectedOptions.mediaSource?.Id,
|
||||
subtitleStreamIndex: selectedOptions.subtitleIndex,
|
||||
});
|
||||
|
||||
if (!data?.url) {
|
||||
console.warn("No URL returned from getStreamUrl", data);
|
||||
Alert.alert(
|
||||
t("player.client_error"),
|
||||
t("player.could_not_create_stream_for_chromecast")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
client
|
||||
.loadMedia({
|
||||
mediaInfo: {
|
||||
contentUrl: data?.url,
|
||||
contentType: "video/mp4",
|
||||
metadata:
|
||||
item.Type === "Episode"
|
||||
? {
|
||||
type: "tvShow",
|
||||
title: item.Name || "",
|
||||
episodeNumber: item.IndexNumber || 0,
|
||||
seasonNumber: item.ParentIndexNumber || 0,
|
||||
seriesTitle: item.SeriesName || "",
|
||||
images: [
|
||||
{
|
||||
url: getParentBackdropImageUrl({
|
||||
api,
|
||||
item,
|
||||
quality: 90,
|
||||
width: 2000,
|
||||
})!,
|
||||
},
|
||||
],
|
||||
}
|
||||
: item.Type === "Movie"
|
||||
? {
|
||||
type: "movie",
|
||||
title: item.Name || "",
|
||||
subtitle: item.Overview || "",
|
||||
images: [
|
||||
{
|
||||
url: getPrimaryImageUrl({
|
||||
api,
|
||||
item,
|
||||
quality: 90,
|
||||
width: 2000,
|
||||
})!,
|
||||
},
|
||||
],
|
||||
}
|
||||
: {
|
||||
type: "generic",
|
||||
title: item.Name || "",
|
||||
subtitle: item.Overview || "",
|
||||
images: [
|
||||
{
|
||||
url: getPrimaryImageUrl({
|
||||
api,
|
||||
item,
|
||||
quality: 90,
|
||||
width: 2000,
|
||||
})!,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
startTime: 0,
|
||||
})
|
||||
.then(() => {
|
||||
// state is already set when reopening current media, so skip it here.
|
||||
if (isOpeningCurrentlyPlayingMedia) {
|
||||
return;
|
||||
}
|
||||
CastContext.showExpandedControls();
|
||||
if (!Platform.isTV) {
|
||||
await CastContext.getPlayServicesState().then(async (state) => {
|
||||
if (state && state !== PlayServicesState.SUCCESS)
|
||||
CastContext.showPlayServicesErrorDialog(state);
|
||||
else {
|
||||
// Get a new URL with the Chromecast device profile:
|
||||
const data = await getStreamUrl({
|
||||
api,
|
||||
item,
|
||||
deviceProfile: chromecastProfile,
|
||||
startTimeTicks: item?.UserData?.PlaybackPositionTicks!,
|
||||
userId: user?.Id,
|
||||
audioStreamIndex: selectedOptions.audioIndex,
|
||||
maxStreamingBitrate: selectedOptions.bitrate?.value,
|
||||
mediaSourceId: selectedOptions.mediaSource?.Id,
|
||||
subtitleStreamIndex: selectedOptions.subtitleIndex,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!data?.url) {
|
||||
console.warn("No URL returned from getStreamUrl", data);
|
||||
Alert.alert(
|
||||
t("player.client_error"),
|
||||
t("player.could_not_create_stream_for_chromecast")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
client
|
||||
.loadMedia({
|
||||
mediaInfo: {
|
||||
contentUrl: data?.url,
|
||||
contentType: "video/mp4",
|
||||
metadata:
|
||||
item.Type === "Episode"
|
||||
? {
|
||||
type: "tvShow",
|
||||
title: item.Name || "",
|
||||
episodeNumber: item.IndexNumber || 0,
|
||||
seasonNumber: item.ParentIndexNumber || 0,
|
||||
seriesTitle: item.SeriesName || "",
|
||||
images: [
|
||||
{
|
||||
url: getParentBackdropImageUrl({
|
||||
api,
|
||||
item,
|
||||
quality: 90,
|
||||
width: 2000,
|
||||
})!,
|
||||
},
|
||||
],
|
||||
}
|
||||
: item.Type === "Movie"
|
||||
? {
|
||||
type: "movie",
|
||||
title: item.Name || "",
|
||||
subtitle: item.Overview || "",
|
||||
images: [
|
||||
{
|
||||
url: getPrimaryImageUrl({
|
||||
api,
|
||||
item,
|
||||
quality: 90,
|
||||
width: 2000,
|
||||
})!,
|
||||
},
|
||||
],
|
||||
}
|
||||
: {
|
||||
type: "generic",
|
||||
title: item.Name || "",
|
||||
subtitle: item.Overview || "",
|
||||
images: [
|
||||
{
|
||||
url: getPrimaryImageUrl({
|
||||
api,
|
||||
item,
|
||||
quality: 90,
|
||||
width: 2000,
|
||||
})!,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
startTime: 0,
|
||||
})
|
||||
.then(() => {
|
||||
// state is already set when reopening current media, so skip it here.
|
||||
if (isOpeningCurrentlyPlayingMedia) {
|
||||
return;
|
||||
}
|
||||
CastContext.showExpandedControls();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
goToPlayer(queryString, selectedOptions.bitrate?.value);
|
||||
|
||||
251
components/PlayButton.tv.tsx
Normal file
251
components/PlayButton.tv.tsx
Normal file
@@ -0,0 +1,251 @@
|
||||
import { Platform } from "react-native";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { itemThemeColorAtom } from "@/utils/atoms/primaryColor";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import { runtimeTicksToMinutes } from "@/utils/time";
|
||||
import { useActionSheet } from "@expo/react-native-action-sheet";
|
||||
import { Feather, Ionicons, MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import { useRouter } from "expo-router";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { Alert, TouchableOpacity, View } from "react-native";
|
||||
import Animated, {
|
||||
Easing,
|
||||
interpolate,
|
||||
interpolateColor,
|
||||
useAnimatedReaction,
|
||||
useAnimatedStyle,
|
||||
useDerivedValue,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from "react-native-reanimated";
|
||||
import { Button } from "./Button";
|
||||
import { SelectedOptions } from "./ItemContent";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHaptic } from "@/hooks/useHaptic";
|
||||
|
||||
interface Props extends React.ComponentProps<typeof Button> {
|
||||
item: BaseItemDto;
|
||||
selectedOptions: SelectedOptions;
|
||||
}
|
||||
|
||||
const ANIMATION_DURATION = 500;
|
||||
const MIN_PLAYBACK_WIDTH = 15;
|
||||
|
||||
export const PlayButton: React.FC<Props> = ({
|
||||
item,
|
||||
selectedOptions,
|
||||
...props
|
||||
}: Props) => {
|
||||
const { showActionSheetWithOptions } = useActionSheet();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [colorAtom] = useAtom(itemThemeColorAtom);
|
||||
const api = useAtomValue(apiAtom);
|
||||
const user = useAtomValue(userAtom);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const startWidth = useSharedValue(0);
|
||||
const targetWidth = useSharedValue(0);
|
||||
const endColor = useSharedValue(colorAtom);
|
||||
const startColor = useSharedValue(colorAtom);
|
||||
const widthProgress = useSharedValue(0);
|
||||
const colorChangeProgress = useSharedValue(0);
|
||||
const [settings] = useSettings();
|
||||
const lightHapticFeedback = useHaptic("light");
|
||||
|
||||
const goToPlayer = useCallback(
|
||||
(q: string, bitrateValue: number | undefined) => {
|
||||
if (!bitrateValue) {
|
||||
router.push(`/player/direct-player?${q}`);
|
||||
return;
|
||||
}
|
||||
router.push(`/player/transcoding-player?${q}`);
|
||||
},
|
||||
[router]
|
||||
);
|
||||
|
||||
const onPress = useCallback(async () => {
|
||||
if (!item) return;
|
||||
|
||||
lightHapticFeedback();
|
||||
|
||||
const queryParams = new URLSearchParams({
|
||||
itemId: item.Id!,
|
||||
audioIndex: selectedOptions.audioIndex?.toString() ?? "",
|
||||
subtitleIndex: selectedOptions.subtitleIndex?.toString() ?? "",
|
||||
mediaSourceId: selectedOptions.mediaSource?.Id ?? "",
|
||||
bitrateValue: selectedOptions.bitrate?.value?.toString() ?? "",
|
||||
});
|
||||
|
||||
const queryString = queryParams.toString();
|
||||
goToPlayer(queryString, selectedOptions.bitrate?.value);
|
||||
return;
|
||||
}, [
|
||||
item,
|
||||
settings,
|
||||
api,
|
||||
user,
|
||||
router,
|
||||
showActionSheetWithOptions,
|
||||
selectedOptions,
|
||||
]);
|
||||
|
||||
const derivedTargetWidth = useDerivedValue(() => {
|
||||
if (!item || !item.RunTimeTicks) return 0;
|
||||
const userData = item.UserData;
|
||||
if (userData && userData.PlaybackPositionTicks) {
|
||||
return userData.PlaybackPositionTicks > 0
|
||||
? Math.max(
|
||||
(userData.PlaybackPositionTicks / item.RunTimeTicks) * 100,
|
||||
MIN_PLAYBACK_WIDTH
|
||||
)
|
||||
: 0;
|
||||
}
|
||||
return 0;
|
||||
}, [item]);
|
||||
|
||||
useAnimatedReaction(
|
||||
() => derivedTargetWidth.value,
|
||||
(newWidth) => {
|
||||
targetWidth.value = newWidth;
|
||||
widthProgress.value = 0;
|
||||
widthProgress.value = withTiming(1, {
|
||||
duration: ANIMATION_DURATION,
|
||||
easing: Easing.bezier(0.7, 0, 0.3, 1.0),
|
||||
});
|
||||
},
|
||||
[item]
|
||||
);
|
||||
|
||||
useAnimatedReaction(
|
||||
() => colorAtom,
|
||||
(newColor) => {
|
||||
endColor.value = newColor;
|
||||
colorChangeProgress.value = 0;
|
||||
colorChangeProgress.value = withTiming(1, {
|
||||
duration: ANIMATION_DURATION,
|
||||
easing: Easing.bezier(0.9, 0, 0.31, 0.99),
|
||||
});
|
||||
},
|
||||
[colorAtom]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const timeout_2 = setTimeout(() => {
|
||||
startColor.value = colorAtom;
|
||||
startWidth.value = targetWidth.value;
|
||||
}, ANIMATION_DURATION);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout_2);
|
||||
};
|
||||
}, [colorAtom, item]);
|
||||
|
||||
/**
|
||||
* ANIMATED STYLES
|
||||
*/
|
||||
const animatedAverageStyle = useAnimatedStyle(() => ({
|
||||
backgroundColor: interpolateColor(
|
||||
colorChangeProgress.value,
|
||||
[0, 1],
|
||||
[startColor.value.primary, endColor.value.primary]
|
||||
),
|
||||
}));
|
||||
|
||||
const animatedPrimaryStyle = useAnimatedStyle(() => ({
|
||||
backgroundColor: interpolateColor(
|
||||
colorChangeProgress.value,
|
||||
[0, 1],
|
||||
[startColor.value.primary, endColor.value.primary]
|
||||
),
|
||||
}));
|
||||
|
||||
const animatedWidthStyle = useAnimatedStyle(() => ({
|
||||
width: `${interpolate(
|
||||
widthProgress.value,
|
||||
[0, 1],
|
||||
[startWidth.value, targetWidth.value]
|
||||
)}%`,
|
||||
}));
|
||||
|
||||
const animatedTextStyle = useAnimatedStyle(() => ({
|
||||
color: interpolateColor(
|
||||
colorChangeProgress.value,
|
||||
[0, 1],
|
||||
[startColor.value.text, endColor.value.text]
|
||||
),
|
||||
}));
|
||||
/**
|
||||
* *********************
|
||||
*/
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TouchableOpacity
|
||||
disabled={!item}
|
||||
accessibilityLabel="Play button"
|
||||
accessibilityHint="Tap to play the media"
|
||||
onPress={onPress}
|
||||
className={`relative`}
|
||||
{...props}
|
||||
>
|
||||
<View className="absolute w-full h-full top-0 left-0 rounded-xl z-10 overflow-hidden">
|
||||
<Animated.View
|
||||
style={[
|
||||
animatedPrimaryStyle,
|
||||
animatedWidthStyle,
|
||||
{
|
||||
height: "100%",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Animated.View
|
||||
style={[animatedAverageStyle, { opacity: 0.5 }]}
|
||||
className="absolute w-full h-full top-0 left-0 rounded-xl"
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
borderWidth: 1,
|
||||
borderColor: colorAtom.primary,
|
||||
borderStyle: "solid",
|
||||
}}
|
||||
className="flex flex-row items-center justify-center bg-transparent rounded-xl z-20 h-12 w-full "
|
||||
>
|
||||
<View className="flex flex-row items-center space-x-2">
|
||||
<Animated.Text style={[animatedTextStyle, { fontWeight: "bold" }]}>
|
||||
{runtimeTicksToMinutes(item?.RunTimeTicks)}
|
||||
</Animated.Text>
|
||||
<Animated.Text style={animatedTextStyle}>
|
||||
<Ionicons name="play-circle" size={24} />
|
||||
</Animated.Text>
|
||||
{settings?.openInVLC && (
|
||||
<Animated.Text style={animatedTextStyle}>
|
||||
<MaterialCommunityIcons
|
||||
name="vlc"
|
||||
size={18}
|
||||
color={animatedTextStyle.color}
|
||||
/>
|
||||
</Animated.Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
{/* <View className="mt-2 flex flex-row items-center">
|
||||
<Ionicons
|
||||
name="information-circle"
|
||||
size={12}
|
||||
className=""
|
||||
color={"#9BA1A6"}
|
||||
/>
|
||||
<Text className="text-neutral-500 ml-1">
|
||||
{directStream ? "Direct stream" : "Transcoded stream"}
|
||||
</Text>
|
||||
</View> */}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import {DownloadMethod, useSettings} from "@/utils/atoms/settings";
|
||||
import { DownloadMethod, useSettings } from "@/utils/atoms/settings";
|
||||
import { getOrSetDeviceId } from "@/utils/device";
|
||||
import { useLog, writeToLog } from "@/utils/log";
|
||||
import {
|
||||
@@ -13,12 +13,6 @@ import {
|
||||
BaseItemDto,
|
||||
MediaSourceInfo,
|
||||
} from "@jellyfin/sdk/lib/generated-client/models";
|
||||
// import {
|
||||
// checkForExistingDownloads,
|
||||
// completeHandler,
|
||||
// download,
|
||||
// setConfig,
|
||||
// } from "@kesha-antonov/react-native-background-downloader";
|
||||
const BackGroundDownloader = !Platform.isTV
|
||||
? require("@kesha-antonov/react-native-background-downloader")
|
||||
: null;
|
||||
@@ -146,15 +140,20 @@ function useDownloadProvider() {
|
||||
if (settings.autoDownload) {
|
||||
startDownload(job);
|
||||
} else {
|
||||
toast.info(t("home.downloads.toasts.item_is_ready_to_be_downloaded",{item: job.item.Name}), {
|
||||
action: {
|
||||
label: t("home.downloads.toasts.go_to_downloads"),
|
||||
onClick: () => {
|
||||
router.push("/downloads");
|
||||
toast.dismiss();
|
||||
toast.info(
|
||||
t("home.downloads.toasts.item_is_ready_to_be_downloaded", {
|
||||
item: job.item.Name,
|
||||
}),
|
||||
{
|
||||
action: {
|
||||
label: t("home.downloads.toasts.go_to_downloads"),
|
||||
onClick: () => {
|
||||
router.push("/downloads");
|
||||
toast.dismiss();
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
Notifications.scheduleNotificationAsync({
|
||||
content: {
|
||||
title: job.item.Name,
|
||||
@@ -231,15 +230,20 @@ function useDownloadProvider() {
|
||||
},
|
||||
});
|
||||
|
||||
toast.info(t("home.downloads.toasts.download_stated_for_item", {item: process.item.Name}), {
|
||||
action: {
|
||||
label: t("home.downloads.toasts.go_to_downloads"),
|
||||
onClick: () => {
|
||||
router.push("/downloads");
|
||||
toast.dismiss();
|
||||
toast.info(
|
||||
t("home.downloads.toasts.download_stated_for_item", {
|
||||
item: process.item.Name,
|
||||
}),
|
||||
{
|
||||
action: {
|
||||
label: t("home.downloads.toasts.go_to_downloads"),
|
||||
onClick: () => {
|
||||
router.push("/downloads");
|
||||
toast.dismiss();
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const baseDirectory = FileSystem.documentDirectory;
|
||||
|
||||
@@ -282,16 +286,21 @@ function useDownloadProvider() {
|
||||
process.item,
|
||||
doneHandler.bytesDownloaded
|
||||
);
|
||||
toast.success(t("home.downloads.toasts.download_completed_for_item", {item: process.item.Name}), {
|
||||
duration: 3000,
|
||||
action: {
|
||||
label: t("home.downloads.toasts.go_to_downloads"),
|
||||
onClick: () => {
|
||||
router.push("/downloads");
|
||||
toast.dismiss();
|
||||
toast.success(
|
||||
t("home.downloads.toasts.download_completed_for_item", {
|
||||
item: process.item.Name,
|
||||
}),
|
||||
{
|
||||
duration: 3000,
|
||||
action: {
|
||||
label: t("home.downloads.toasts.go_to_downloads"),
|
||||
onClick: () => {
|
||||
router.push("/downloads");
|
||||
toast.dismiss();
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
setTimeout(() => {
|
||||
BackGroundDownloader.completeHandler(process.id);
|
||||
removeProcess(process.id);
|
||||
@@ -307,7 +316,12 @@ function useDownloadProvider() {
|
||||
if (error.errorCode === 404) {
|
||||
errorMsg = "File not found on server";
|
||||
}
|
||||
toast.error(t("home.downloads.toasts.download_failed_for_item", {item: process.item.Name, error: errorMsg}));
|
||||
toast.error(
|
||||
t("home.downloads.toasts.download_failed_for_item", {
|
||||
item: process.item.Name,
|
||||
error: errorMsg,
|
||||
})
|
||||
);
|
||||
writeToLog("ERROR", `Download failed for ${process.item.Name}`, {
|
||||
error,
|
||||
processDetails: {
|
||||
@@ -364,15 +378,20 @@ function useDownloadProvider() {
|
||||
throw new Error("Failed to start optimization job");
|
||||
}
|
||||
|
||||
toast.success(t("home.downloads.toasts.queued_item_for_optimization", {item: item.Name}), {
|
||||
action: {
|
||||
label: t("home.downloads.toasts.go_to_downloads"),
|
||||
onClick: () => {
|
||||
router.push("/downloads");
|
||||
toast.dismiss();
|
||||
toast.success(
|
||||
t("home.downloads.toasts.queued_item_for_optimization", {
|
||||
item: item.Name,
|
||||
}),
|
||||
{
|
||||
action: {
|
||||
label: t("home.downloads.toasts.go_to_downloads"),
|
||||
onClick: () => {
|
||||
router.push("/downloads");
|
||||
toast.dismiss();
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
writeToLog("ERROR", "Error in startBackgroundDownload", error);
|
||||
console.error("Error in startBackgroundDownload:", error);
|
||||
@@ -384,11 +403,16 @@ function useDownloadProvider() {
|
||||
headers: error.response?.headers,
|
||||
});
|
||||
toast.error(
|
||||
t("home.downloads.toasts.failed_to_start_download_for_item", {item: item.Name, message: error.message})
|
||||
t("home.downloads.toasts.failed_to_start_download_for_item", {
|
||||
item: item.Name,
|
||||
message: error.message,
|
||||
})
|
||||
);
|
||||
if (error.response) {
|
||||
toast.error(
|
||||
t("home.downloads.toasts.server_responded_with_status", {statusCode: error.response.status})
|
||||
t("home.downloads.toasts.server_responded_with_status", {
|
||||
statusCode: error.response.status,
|
||||
})
|
||||
);
|
||||
} else if (error.request) {
|
||||
t("home.downloads.toasts.no_response_received_from_server");
|
||||
@@ -398,7 +422,10 @@ function useDownloadProvider() {
|
||||
} else {
|
||||
console.error("Non-Axios error:", error);
|
||||
toast.error(
|
||||
t("home.downloads.toasts.failed_to_start_download_for_item_unexpected_error", {item: item.Name})
|
||||
t(
|
||||
"home.downloads.toasts.failed_to_start_download_for_item_unexpected_error",
|
||||
{ item: item.Name }
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -414,11 +441,19 @@ function useDownloadProvider() {
|
||||
queryClient.invalidateQueries({ queryKey: ["downloadedItems"] }),
|
||||
])
|
||||
.then(() =>
|
||||
toast.success(t("home.downloads.toasts.all_files_folders_and_jobs_deleted_successfully"))
|
||||
toast.success(
|
||||
t(
|
||||
"home.downloads.toasts.all_files_folders_and_jobs_deleted_successfully"
|
||||
)
|
||||
)
|
||||
)
|
||||
.catch((reason) => {
|
||||
console.error("Failed to delete all files, folders, and jobs:", reason);
|
||||
toast.error(t("home.downloads.toasts.an_error_occured_while_deleting_files_and_jobs"));
|
||||
toast.error(
|
||||
t(
|
||||
"home.downloads.toasts.an_error_occured_while_deleting_files_and_jobs"
|
||||
)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user