chore: linting fixes && github actions for linting (#612)

This commit is contained in:
Ahmed Sbai
2025-03-31 07:44:10 +02:00
committed by GitHub
parent 16b834cf71
commit b9bb109f4a
105 changed files with 604 additions and 570 deletions

28
.github/workflows/lint.yaml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Lint
on:
pull_request:
branches: [ develop, master ]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20.x'
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Run linting checks
run: bun run check

View File

@@ -86,6 +86,7 @@ We welcome any help to make Streamyfin better. If you'd like to contribute, plea
1. Use node `>20`
2. Install dependencies `bun i && bun run submodule-reload`
3. Make sure you have xcode and/or android studio installed. (follow the guides for expo: https://docs.expo.dev/workflow/android-studio-emulator/)
4. Install BiomeJS extension in VSCode/Your IDE (https://biomejs.dev/)
4. run `npm run prebuild`
5. Create an expo dev build by running `npm run ios` or `npm run android`. This will open a simulator on your computer and run the app.

View File

@@ -26,11 +26,11 @@ export default function menuLinks() {
const getMenuLinks = useCallback(async () => {
try {
const response = await api?.axiosInstance.get(
api?.basePath + "/web/config.json",
`${api?.basePath}/web/config.json`,
);
const config = response?.data;
if (!config && !config.hasOwnProperty("menuLinks")) {
if (!config && !Object.hasOwn(config, "menuLinks")) {
console.error("Menu links not found");
return;
}

View File

@@ -17,7 +17,7 @@ export default function SearchLayout() {
backgroundColor: "black",
},
headerBlurEffect: "prominent",
headerTransparent: Platform.OS === "ios" ? true : false,
headerTransparent: Platform.OS === "ios",
headerShadowVisible: false,
}}
/>

View File

@@ -32,7 +32,7 @@ export default function IndexLayout() {
{!Platform.isTV && (
<>
<Chromecast.Chromecast />
{user && user.Policy?.IsAdministrator && <SessionsButton />}
{user?.Policy?.IsAdministrator && <SessionsButton />}
<SettingsButton />
</>
)}

View File

@@ -29,7 +29,7 @@ export default function page() {
try {
return (
downloadedFiles
?.filter((f) => f.item.SeriesId == seriesId)
?.filter((f) => f.item.SeriesId === seriesId)
?.sort(
(a, b) => a?.item.ParentIndexNumber! - b.item.ParentIndexNumber!,
) || []

View File

@@ -36,7 +36,7 @@ export default function page() {
</View>
);
if (!sessions || sessions.length == 0)
if (!sessions || sessions.length === 0)
return (
<View className='h-full w-full flex justify-center items-center'>
<Text className='text-lg text-neutral-500'>
@@ -175,7 +175,7 @@ const SessionCard = ({ session }: SessionCardProps) => {
</View>
<View className='align-bottom bg-gray-800 h-1'>
<View
className={`bg-purple-600 h-full`}
className={"bg-purple-600 h-full"}
style={{
width: `${getProgressPercentage()}%`,
}}
@@ -298,7 +298,7 @@ const TranscodingStreamView = ({
const TranscodingView = ({ session }: SessionCardProps) => {
const videoStream = useMemo(() => {
return session.NowPlayingItem?.MediaStreams?.filter(
(s) => s.Type == "Video",
(s) => s.Type === "Video",
)[0];
}, [session]);
@@ -318,7 +318,7 @@ const TranscodingView = ({ session }: SessionCardProps) => {
const isTranscoding = useMemo(() => {
return (
session.PlayState?.PlayMethod == "Transcode" && session.TranscodingInfo
session.PlayState?.PlayMethod === "Transcode" && session.TranscodingInfo
);
}, [session.PlayState?.PlayMethod, session.TranscodingInfo]);
@@ -341,9 +341,7 @@ const TranscodingView = ({ session }: SessionCardProps) => {
codec: session.TranscodingInfo?.VideoCodec,
}}
isTranscoding={
isTranscoding && !session.TranscodingInfo?.IsVideoDirect
? true
: false
!!(isTranscoding && !session.TranscodingInfo?.IsVideoDirect)
}
/>
@@ -360,14 +358,11 @@ const TranscodingView = ({ session }: SessionCardProps) => {
audioChannels: session.TranscodingInfo?.AudioChannels?.toString(),
}}
isTranscoding={
isTranscoding && !session.TranscodingInfo?.IsVideoDirect
? true
: false
!!(isTranscoding && !session.TranscodingInfo?.IsVideoDirect)
}
/>
{subtitleStream && (
<>
<TranscodingStreamView
title='Subtitle'
isTranscoding={false}
@@ -377,7 +372,6 @@ const TranscodingView = ({ session }: SessionCardProps) => {
}}
transcodeValue={null}
/>
</>
)}
</View>
);

View File

@@ -1,73 +1,77 @@
import { Loader } from "@/components/Loader";
import { Text } from "@/components/common/Text";
import {LogLevel, useLog, writeErrorLog} from "@/utils/log";
import { FilterButton } from "@/components/filters/FilterButton";
import { LogLevel, useLog, writeErrorLog } from "@/utils/log";
import * as FileSystem from "expo-file-system";
import { useNavigation } from "expo-router";
import * as Sharing from "expo-sharing";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import {ScrollView, TouchableOpacity, View} from "react-native";
import { ScrollView, TouchableOpacity, View } from "react-native";
import Collapsible from "react-native-collapsible";
import React, {useCallback, useEffect, useMemo, useState} from "react";
import {FilterButton} from "@/components/filters/FilterButton";
import {useNavigation} from "expo-router";
import * as FileSystem from 'expo-file-system';
import * as Sharing from 'expo-sharing';
import {Loader} from "@/components/Loader";
export default function page() {
const navigation = useNavigation();
const { logs } = useLog();
const { t } = useTranslation();
const defaultLevels: LogLevel[] = ["INFO", "ERROR", "DEBUG", "WARN"]
const defaultLevels: LogLevel[] = ["INFO", "ERROR", "DEBUG", "WARN"];
const codeBlockStyle = {
backgroundColor: '#000',
backgroundColor: "#000",
padding: 10,
fontFamily: 'monospace',
maxHeight: 300
}
fontFamily: "monospace",
maxHeight: 300,
};
const [loading, setLoading] = useState<boolean>(false)
const [state, setState] = useState<Record<string, boolean>>({})
const [loading, setLoading] = useState<boolean>(false);
const [state, setState] = useState<Record<string, boolean>>({});
const [order, setOrder] = useState<"asc" | "desc">("desc");
const [levels, setLevels] = useState<LogLevel[]>(defaultLevels);
const filteredLogs = useMemo(
() => logs
?.filter(log => levels.includes(log.level))
() =>
logs
?.filter((log) => levels.includes(log.level))
?.[
// Already in asc order as they are recorded. just reverse for desc
?.[order === "desc" ? "reverse" : "concat"]?.(),
[logs, order, levels]
)
order === "desc" ? "reverse" : "concat"
]?.(),
[logs, order, levels],
);
// Sharing it as txt while its formatted allows us to share it with many more applications
const share = useCallback(async () => {
const uri = FileSystem.documentDirectory + "logs.txt"
const uri = `${FileSystem.documentDirectory}logs.txt`;
setLoading(true)
setLoading(true);
FileSystem.writeAsStringAsync(uri, JSON.stringify(filteredLogs))
.then(() => {
setLoading(false)
Sharing.shareAsync(uri, {mimeType: "txt", UTI: "txt"})
setLoading(false);
Sharing.shareAsync(uri, { mimeType: "txt", UTI: "txt" });
})
.catch((e) => writeErrorLog("Something went wrong attempting to export", e))
.finally(() => setLoading(false))
}, [filteredLogs])
.catch((e) =>
writeErrorLog("Something went wrong attempting to export", e),
)
.finally(() => setLoading(false));
}, [filteredLogs]);
useEffect(() => {
navigation.setOptions({
headerRight: () => (
loading
? <Loader/>
: (
headerRight: () =>
loading ? (
<Loader />
) : (
<TouchableOpacity onPress={share}>
<Text>{t("home.settings.logs.export_logs")}</Text>
</TouchableOpacity>
)
),
});
}, [share, loading]);
return (
<>
<View className="flex flex-row justify-end py-2 px-4 space-x-2">
<View className='flex flex-row justify-end py-2 px-4 space-x-2'>
<FilterButton
id='order'
queryKey='log'
@@ -93,25 +97,30 @@ export default function page() {
<ScrollView className='pb-4 px-4'>
<View className='flex flex-col space-y-2'>
{filteredLogs?.map((log, index) => (
<View
className='bg-neutral-900 rounded-xl p-3'
key={index}
>
<View className='bg-neutral-900 rounded-xl p-3' key={index}>
<TouchableOpacity
disabled={!log.data}
onPress={() => setState((v) => ({...v, [log.timestamp]: !v[log.timestamp]}))}
onPress={() =>
setState((v) => ({
...v,
[log.timestamp]: !v[log.timestamp],
}))
}
>
<View className="flex flex-row justify-between">
<View className='flex flex-row justify-between'>
<Text
className={`mb-1
${log.level === "INFO" && "text-blue-500"}
${log.level === "ERROR" && "text-red-500"}
${log.level === "DEBUG" && "text-purple-500"}
`}>
`}
>
{log.level}
</Text>
<Text className="text-xs">{new Date(log.timestamp).toLocaleString()}</Text>
<Text className='text-xs'>
{new Date(log.timestamp).toLocaleString()}
</Text>
</View>
<Text uiTextView selectable className='text-xs'>
{log.message}
@@ -121,14 +130,14 @@ export default function page() {
{log.data && (
<>
{!state[log.timestamp] && (
<Text className="text-xs mt-0.5">{t("home.settings.logs.click_for_more_info")}</Text>
<Text className='text-xs mt-0.5'>
{t("home.settings.logs.click_for_more_info")}
</Text>
)}
<Collapsible collapsed={!state[log.timestamp]}>
<View className="mt-2 flex flex-col space-y-2">
<ScrollView className="rounded-xl" style={codeBlockStyle}>
<Text>
{JSON.stringify(log.data, null, 2)}
</Text>
<View className='mt-2 flex flex-col space-y-2'>
<ScrollView className='rounded-xl' style={codeBlockStyle}>
<Text>{JSON.stringify(log.data, null, 2)}</Text>
</ScrollView>
</View>
</Collapsible>

View File

@@ -93,7 +93,7 @@ export default function page() {
showText={!pluginSettings?.searchEngine?.locked}
className='mt-2 flex flex-col rounded-xl overflow-hidden pl-4 bg-neutral-900 px-4'
>
<View className={`flex flex-row items-center bg-neutral-900 h-11 pr-4`}>
<View className={"flex flex-row items-center bg-neutral-900 h-11 pr-4"}>
<Text className='mr-4'>
{t("home.settings.plugins.marlin_search.url")}
</Text>

View File

@@ -31,7 +31,7 @@ export default function page() {
return;
}
const updatedUrl = newVal.endsWith("/") ? newVal : newVal + "/";
const updatedUrl = newVal.endsWith("/") ? newVal : `${newVal}/`;
updateSettings({
optimizedVersionsServerUrl: updatedUrl,

View File

@@ -133,7 +133,7 @@ const page: React.FC = () => {
queryFn={fetchItems}
queryKey={["actor", "movies", actorId]}
/>
<View className='h-12'></View>
<View className='h-12' />
</View>
</ParallaxScrollView>
);

View File

@@ -157,9 +157,8 @@ const page: React.FC = () => {
if (accumulatedItems < totalItems) {
return lastPage?.Items?.length * pages.length;
} else {
return undefined;
}
return undefined;
},
initialPageParam: 0,
enabled: !!api && !!user?.Id && !!collection,
@@ -412,7 +411,7 @@ const page: React.FC = () => {
width: 10,
height: 10,
}}
></View>
/>
)}
/>
);

View File

@@ -93,19 +93,19 @@ const Page: React.FC = () => {
height: item?.Type === "Episode" ? 300 : 450,
}}
className='bg-transparent rounded-lg mb-4 w-full'
></View>
<View className='h-6 bg-neutral-900 rounded mb-4 w-14'></View>
<View className='h-10 bg-neutral-900 rounded-lg mb-2 w-1/2'></View>
<View className='h-3 bg-neutral-900 rounded mb-3 w-8'></View>
/>
<View className='h-6 bg-neutral-900 rounded mb-4 w-14' />
<View className='h-10 bg-neutral-900 rounded-lg mb-2 w-1/2' />
<View className='h-3 bg-neutral-900 rounded mb-3 w-8' />
<View className='flex flex-row space-x-1 mb-8'>
<View className='h-6 bg-neutral-900 rounded mb-3 w-14'></View>
<View className='h-6 bg-neutral-900 rounded mb-3 w-14'></View>
<View className='h-6 bg-neutral-900 rounded mb-3 w-14'></View>
<View className='h-6 bg-neutral-900 rounded mb-3 w-14' />
<View className='h-6 bg-neutral-900 rounded mb-3 w-14' />
<View className='h-6 bg-neutral-900 rounded mb-3 w-14' />
</View>
<View className='h-3 bg-neutral-900 rounded w-2/3 mb-1'></View>
<View className='h-10 bg-neutral-900 rounded-lg w-full mb-2'></View>
<View className='h-12 bg-neutral-900 rounded-lg w-full mb-2'></View>
<View className='h-24 bg-neutral-900 rounded-lg mb-1 w-full'></View>
<View className='h-3 bg-neutral-900 rounded w-2/3 mb-1' />
<View className='h-10 bg-neutral-900 rounded-lg w-full mb-2' />
<View className='h-12 bg-neutral-900 rounded-lg w-full mb-2' />
<View className='h-24 bg-neutral-900 rounded-lg mb-1 w-full' />
</Animated.View>
{item && <ItemContent item={item} />}
</View>

View File

@@ -33,9 +33,11 @@ export default function page() {
};
return jellyseerrApi?.discover(
(type == DiscoverSliderType.NETWORKS
`${
type === DiscoverSliderType.NETWORKS
? Endpoints.DISCOVER_TV_NETWORK
: Endpoints.DISCOVER_MOVIES_STUDIO) + `/${companyId}`,
: Endpoints.DISCOVER_MOVIES_STUDIO
}/${companyId}`,
params,
);
},

View File

@@ -36,7 +36,7 @@ export default function page() {
};
return jellyseerrApi?.discover(
type == DiscoverSliderType.MOVIE_GENRES
type === DiscoverSliderType.MOVIE_GENRES
? Endpoints.DISCOVER_MOVIES
: Endpoints.DISCOVER_TV,
params,

View File

@@ -240,7 +240,7 @@ const Page: React.FC = () => {
<GenreTags genres={details?.genres?.map((g) => g.name) || []} />
</View>
{isLoading || isFetching ? (
<Button loading={true} disabled={true} color='purple'></Button>
<Button loading={true} disabled={true} color='purple' />
) : canRequest ? (
<Button color='purple' onPress={request}>
{t("jellyseerr.request_button")}

View File

@@ -124,7 +124,7 @@ export default function page() {
height: HOUR_HEIGHT,
}}
className='bg-neutral-800'
></View>
/>
{channels?.Items?.map((c, i) => (
<View className='h-16 w-16 mr-4 rounded-lg overflow-hidden' key={i}>
<ItemImage

View File

@@ -87,7 +87,6 @@ const page: React.FC = () => {
<View className='flex flex-row items-center space-x-2'>
<AddToFavorites item={item} />
{!Platform.isTV && (
<>
<DownloadItems
size='large'
title={t("item_card.download.download_series")}
@@ -103,7 +102,6 @@ const page: React.FC = () => {
/>
)}
/>
</>
)}
</View>
),
@@ -127,8 +125,7 @@ const page: React.FC = () => {
/>
}
logo={
<>
{logoUrl ? (
logoUrl ? (
<Image
source={{
uri: logoUrl,
@@ -139,8 +136,7 @@ const page: React.FC = () => {
resizeMode: "contain",
}}
/>
) : null}
</>
) : null
}
>
<View className='flex flex-col pt-4'>

View File

@@ -216,9 +216,8 @@ const Page = () => {
if (accumulatedItems < totalItems) {
return lastPage?.Items?.length * pages.length;
} else {
return undefined;
}
return undefined;
},
initialPageParam: 0,
enabled: !!api && !!user?.Id && !!library,
@@ -478,7 +477,7 @@ const Page = () => {
width: 10,
height: 10,
}}
></View>
/>
)}
/>
);

View File

@@ -25,7 +25,7 @@ export default function IndexLayout() {
headerLargeStyle: {
backgroundColor: "black",
},
headerTransparent: Platform.OS === "ios" ? true : false,
headerTransparent: Platform.OS === "ios",
headerShadowVisible: false,
headerRight: () =>
!pluginSettings?.libraryOptions?.locked &&
@@ -159,7 +159,7 @@ export default function IndexLayout() {
updateSettings({
libraryOptions: {
...settings.libraryOptions,
showTitles: newValue === "on" ? true : false,
showTitles: newValue === "on",
},
});
}}
@@ -176,7 +176,7 @@ export default function IndexLayout() {
updateSettings({
libraryOptions: {
...settings.libraryOptions,
showStats: newValue === "on" ? true : false,
showStats: newValue === "on",
},
});
}}
@@ -200,7 +200,7 @@ export default function IndexLayout() {
title: "",
headerShown: true,
headerBlurEffect: "prominent",
headerTransparent: Platform.OS === "ios" ? true : false,
headerTransparent: Platform.OS === "ios",
headerShadowVisible: false,
}}
/>
@@ -213,7 +213,7 @@ export default function IndexLayout() {
title: "",
headerShown: true,
headerBlurEffect: "prominent",
headerTransparent: Platform.OS === "ios" ? true : false,
headerTransparent: Platform.OS === "ios",
headerShadowVisible: false,
}}
/>

View File

@@ -100,7 +100,7 @@ export default function index() {
height: StyleSheet.hairlineWidth,
}}
className='bg-neutral-800 mx-2 my-4'
></View>
/>
) : (
<View className='h-4' />
)

View File

@@ -20,7 +20,7 @@ export default function SearchLayout() {
backgroundColor: "black",
},
headerBlurEffect: "prominent",
headerTransparent: Platform.OS === "ios" ? true : false,
headerTransparent: Platform.OS === "ios",
headerShadowVisible: false,
}}
/>
@@ -33,7 +33,7 @@ export default function SearchLayout() {
title: "",
headerShown: true,
headerBlurEffect: "prominent",
headerTransparent: Platform.OS === "ios" ? true : false,
headerTransparent: Platform.OS === "ios",
headerShadowVisible: false,
}}
/>

View File

@@ -13,8 +13,7 @@ import SeriesPoster from "@/components/posters/SeriesPoster";
import { LoadingSkeleton } from "@/components/search/LoadingSkeleton";
import { SearchItemWrapper } from "@/components/search/SearchItemWrapper";
import { useJellyseerr } from "@/hooks/useJellyseerr";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { sortOrderOptions } from "@/utils/atoms/filters";
import { apiAtom } from "@/providers/JellyfinProvider";
import { useSettings } from "@/utils/atoms/settings";
import { eventBus } from "@/utils/eventBus";
import type {
@@ -64,7 +63,6 @@ export default function search() {
const [debouncedSearch] = useDebounce(search, 500);
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
const [settings] = useSettings();
const { jellyseerrApi } = useJellyseerr();
@@ -83,7 +81,9 @@ export default function search() {
}, [settings]);
useEffect(() => {
if (q && q.length > 0) setSearch(q);
if (q && q.length > 0) {
setSearch(q);
}
}, [q]);
const searchFn = useCallback(
@@ -94,7 +94,9 @@ export default function search() {
types: BaseItemKind[];
query: string;
}): Promise<BaseItemDto[]> => {
if (!api || !query) return [];
if (!api || !query) {
return [];
}
try {
if (searchEngine === "Jellyfin") {
@@ -105,8 +107,10 @@ export default function search() {
});
return (searchApi.data.SearchHints as BaseItemDto[]) || [];
} else {
if (!settings?.marlinServerUrl) return [];
}
if (!settings?.marlinServerUrl) {
return [];
}
const url = `${
settings.marlinServerUrl
@@ -118,7 +122,9 @@ export default function search() {
const ids = response1.data.ids;
if (!ids || !ids.length) return [];
if (!ids || !ids.length) {
return [];
}
const response2 = await getItemsApi(api).getItems({
ids,
@@ -126,7 +132,6 @@ export default function search() {
});
return (response2.data.Items as BaseItemDto[]) || [];
}
} catch (error) {
console.error("Error during search:", error);
return []; // Ensure an empty array is returned in case of an error
@@ -162,8 +167,10 @@ export default function search() {
useEffect(() => {
const unsubscribe = eventBus.on("searchTabPressed", () => {
// Screen not actuve
if (!searchBarRef.current) return;
// Screen not active
if (!searchBarRef.current) {
return;
}
// Screen is active, focus search bar
searchBarRef.current?.focus();
});
@@ -254,7 +261,6 @@ export default function search() {
}}
>
{jellyseerrApi && (
<>
<ScrollView
horizontal
className='flex flex-row flex-wrap space-x-2 px-4 mb-2'
@@ -287,7 +293,7 @@ export default function search() {
queryKey='jellyseerr_search'
queryFn={async () =>
Object.keys(JellyseerrSearchSort).filter((v) =>
isNaN(Number(v)),
Number.isNaN(Number(v)),
)
}
set={(value) => setJellyseerrOrderBy(value[0])}
@@ -311,7 +317,6 @@ export default function search() {
</View>
)}
</ScrollView>
</>
)}
<View className='mt-2'>
@@ -411,9 +416,8 @@ export default function search() {
/>
)}
{searchType === "Library" && (
<>
{!loading && noResults && debouncedSearch.length > 0 ? (
{searchType === "Library" &&
(!loading && noResults && debouncedSearch.length > 0 ? (
<View>
<Text className='text-center text-lg font-bold mt-4'>
{t("search.no_results_found_for")}
@@ -434,9 +438,7 @@ export default function search() {
</TouchableOpacity>
))}
</View>
) : null}
</>
)}
) : null)}
</View>
</ScrollView>
</>

View File

@@ -72,7 +72,7 @@ export default function TabLayout() {
options={{
title: t("tabs.home"),
tabBarIcon:
Platform.OS == "android"
Platform.OS === "android"
? ({ color, focused, size }) =>
require("@/assets/icons/house.fill.png")
: ({ focused }) =>
@@ -91,7 +91,7 @@ export default function TabLayout() {
options={{
title: t("tabs.search"),
tabBarIcon:
Platform.OS == "android"
Platform.OS === "android"
? ({ color, focused, size }) =>
require("@/assets/icons/magnifyingglass.png")
: ({ focused }) =>
@@ -105,7 +105,7 @@ export default function TabLayout() {
options={{
title: t("tabs.favorites"),
tabBarIcon:
Platform.OS == "android"
Platform.OS === "android"
? ({ color, focused, size }) =>
focused
? require("@/assets/icons/heart.fill.png")
@@ -121,7 +121,7 @@ export default function TabLayout() {
options={{
title: t("tabs.library"),
tabBarIcon:
Platform.OS == "android"
Platform.OS === "android"
? ({ color, focused, size }) =>
require("@/assets/icons/server.rack.png")
: ({ focused }) =>
@@ -135,9 +135,9 @@ export default function TabLayout() {
options={{
title: t("tabs.custom_links"),
// @ts-expect-error
tabBarItemHidden: settings?.showCustomMenuLinks ? false : true,
tabBarItemHidden: !settings?.showCustomMenuLinks,
tabBarIcon:
Platform.OS == "android"
Platform.OS === "android"
? ({ focused }) => require("@/assets/icons/list.png")
: ({ focused }) =>
focused

View File

@@ -16,7 +16,12 @@ import {
BACKGROUND_FETCH_TASK_SESSIONS,
registerBackgroundFetchAsyncSessions,
} from "@/utils/background-tasks";
import {LogProvider, writeDebugLog, writeErrorLog, writeToLog} from "@/utils/log";
import {
LogProvider,
writeDebugLog,
writeErrorLog,
writeToLog,
} from "@/utils/log";
import { storage } from "@/utils/mmkv";
import { cancelJobById, getAllJobsByDeviceId } from "@/utils/optimize-server";
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
@@ -31,8 +36,8 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const BackgroundFetch = !Platform.isTV
? require("expo-background-fetch")
: null;
import * as FileSystem from "expo-file-system";
import * as Device from "expo-device";
import * as FileSystem from "expo-file-system";
const Notifications = !Platform.isTV ? require("expo-notifications") : null;
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
import { Stack, router, useSegments } from "expo-router";
@@ -161,7 +166,7 @@ if (!Platform.isTV) {
for (const job of jobs) {
if (job.status === "completed") {
const downloadUrl = url + "download/" + job.id;
const downloadUrl = `${url}download/${job.id}`;
const tasks = await BackGroundDownloader.checkForExistingDownloads();
if (tasks.find((task: { id: string }) => task.id === job.id)) {
@@ -194,7 +199,7 @@ if (!Platform.isTV) {
title: job.item.Name,
body: "Download completed",
data: {
url: `/downloads`,
url: "/downloads",
},
},
trigger: null,
@@ -208,7 +213,7 @@ if (!Platform.isTV) {
title: job.item.Name,
body: "Download failed",
data: {
url: `/downloads`,
url: "/downloads",
},
},
trigger: null,
@@ -333,7 +338,7 @@ function Layout() {
}
// only create push token for real devices (pointless for emulators)
if(Device.isDevice) {
if (Device.isDevice) {
Notifications?.getExpoPushTokenAsync()
.then((token: ExpoPushToken) => token && setExpoPushToken(token))
.catch((reason: any) => console.log("Failed to get token", reason));
@@ -357,9 +362,12 @@ function Layout() {
Notifications?.addNotificationResponseReceivedListener(
(response: NotificationResponse) => {
// Currently the notifications supported by the plugin will send data for deep links.
const {title, data} = response.notification.request.content;
const { title, data } = response.notification.request.content;
writeDebugLog(`Notification ${title} opened`, response.notification.request.content)
writeDebugLog(
`Notification ${title} opened`,
response.notification.request.content,
);
if (data && Object.keys(data).length > 0) {
const type = data?.type?.toLower?.();
@@ -367,12 +375,12 @@ function Layout() {
switch (type) {
case "movie":
router.push(`/(auth)/(tabs)/home/items/page?id=${itemId}`)
router.push(`/(auth)/(tabs)/home/items/page?id=${itemId}`);
break;
case "episode":
// We just clicked a notification for an individual episode.
if (itemId) {
router.push(`/(auth)/(tabs)/home/items/page?id=${itemId}`)
router.push(`/(auth)/(tabs)/home/items/page?id=${itemId}`);
}
// summarized season notification for multiple episodes. Bring them to series season
else {
@@ -380,10 +388,11 @@ function Layout() {
const seasonIndex = data.seasonIndex;
if (seasonIndex) {
router.push(`/(auth)/(tabs)/home/series/${seriesId}?seasonIndex=${seasonIndex}`)
}
else {
router.push(`/(auth)/(tabs)/home/series/${seriesId}`)
router.push(
`/(auth)/(tabs)/home/series/${seriesId}?seasonIndex=${seasonIndex}`,
);
} else {
router.push(`/(auth)/(tabs)/home/series/${seriesId}`);
}
}
break;

View File

@@ -218,16 +218,14 @@ const Login: React.FC = () => {
<View className='px-4 -mt-20 w-full'>
<View className='flex flex-col space-y-2'>
<Text className='text-2xl font-bold -mb-2'>
<>
{serverName ? (
<>
{t("login.login_to_title") + " "}
{`${t("login.login_to_title")} `}
<Text className='text-purple-600'>{serverName}</Text>
</>
) : (
t("login.login_title")
)}
</>
</Text>
<Text className='text-xs text-neutral-400'>
{api.basePath}
@@ -284,7 +282,7 @@ const Login: React.FC = () => {
</View>
</View>
<View className='absolute bottom-0 left-0 w-full px-4 mb-2'></View>
<View className='absolute bottom-0 left-0 w-full px-4 mb-2' />
</View>
</>
) : (

View File

@@ -17,9 +17,7 @@ Number.prototype.bytesToReadable = function (decimals = 2) {
const i = Math.floor(Math.log(bytes) / Math.log(k));
return (
Number.parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]
);
return `${Number.parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
};
Number.prototype.secondsToMilliseconds = function () {

View File

@@ -9,7 +9,8 @@
"ios",
"android",
"Streamyfin.app",
"utils/jellyseerr"
"utils/jellyseerr",
".expo"
]
},
"linter": {
@@ -17,12 +18,18 @@
"rules": {
"style": {
"useImportType": "off",
"noNonNullAssertion": "off"
"noNonNullAssertion": "off",
"noParameterAssign": "off",
"useLiteralEnumMembers": "off"
},
"complexity": {
"noForEach": "off"
},
"recommended": true,
"correctness": { "useExhaustiveDependencies": "off" },
"suspicious": {
"noExplicitAny": "off"
"noExplicitAny": "off",
"noArrayIndexKey": "off"
}
}
},

View File

@@ -73,7 +73,7 @@ export const Button: React.FC<PropsWithChildren<ButtonProps>> = ({
flex flex-row items-center justify-between w-full
${justify === "between" ? "justify-between" : "justify-center"}`}
>
{iconLeft ? iconLeft : <View className='w-4'></View>}
{iconLeft ? iconLeft : <View className='w-4' />}
<Text
className={`
text-white font-bold text-base
@@ -85,7 +85,7 @@ export const Button: React.FC<PropsWithChildren<ButtonProps>> = ({
>
{children}
</Text>
{iconRight ? iconRight : <View className='w-4'></View>}
{iconRight ? iconRight : <View className='w-4' />}
</View>
)}
</TouchableOpacity>

View File

@@ -27,32 +27,38 @@ const ContinueWatchingPoster: React.FC<ContinueWatchingPosterProps> = ({
* Get horizontal poster for movie and episode, with failover to primary.
*/
const url = useMemo(() => {
if (!api) return;
if (!api) {
return;
}
if (item.Type === "Episode" && useEpisodePoster) {
return `${api?.basePath}/Items/${item.Id}/Images/Primary?fillHeight=389&quality=80`;
}
if (item.Type === "Episode") {
if (item.ParentBackdropItemId && item.ParentThumbImageTag)
if (item.ParentBackdropItemId && item.ParentThumbImageTag) {
return `${api?.basePath}/Items/${item.ParentBackdropItemId}/Images/Thumb?fillHeight=389&quality=80&tag=${item.ParentThumbImageTag}`;
else
}
return `${api?.basePath}/Items/${item.Id}/Images/Primary?fillHeight=389&quality=80`;
}
if (item.Type === "Movie") {
if (item.ImageTags?.["Thumb"])
return `${api?.basePath}/Items/${item.Id}/Images/Thumb?fillHeight=389&quality=80&tag=${item.ImageTags?.["Thumb"]}`;
else
if (item.ImageTags?.Thumb) {
return `${api?.basePath}/Items/${item.Id}/Images/Thumb?fillHeight=389&quality=80&tag=${item.ImageTags?.Thumb}`;
}
return `${api?.basePath}/Items/${item.Id}/Images/Primary?fillHeight=389&quality=80`;
}
if (item.Type === "Program") {
if (item.ImageTags?.["Thumb"])
return `${api?.basePath}/Items/${item.Id}/Images/Thumb?fillHeight=389&quality=80&tag=${item.ImageTags?.["Thumb"]}`;
else
if (item.ImageTags?.Thumb) {
return `${api?.basePath}/Items/${item.Id}/Images/Thumb?fillHeight=389&quality=80&tag=${item.ImageTags?.Thumb}`;
}
return `${api?.basePath}/Items/${item.Id}/Images/Primary?fillHeight=389&quality=80`;
}
if (item.ImageTags?.["Thumb"])
return `${api?.basePath}/Items/${item.Id}/Images/Thumb?fillHeight=389&quality=80&tag=${item.ImageTags?.["Thumb"]}`;
else
if (item.ImageTags?.Thumb) {
return `${api?.basePath}/Items/${item.Id}/Images/Thumb?fillHeight=389&quality=80&tag=${item.ImageTags?.Thumb}`;
}
return `${api?.basePath}/Items/${item.Id}/Images/Primary?fillHeight=389&quality=80`;
}, [item]);
@@ -64,15 +70,12 @@ const ContinueWatchingPoster: React.FC<ContinueWatchingPosterProps> = ({
const total = endDate.getTime() - startDate.getTime();
const elapsed = now.getTime() - startDate.getTime();
return (elapsed / total) * 100;
} else {
return item.UserData?.PlayedPercentage || 0;
}
return item.UserData?.PlayedPercentage || 0;
}, [item]);
if (!url)
return (
<View className='aspect-video border border-neutral-800 w-44'></View>
);
return <View className='aspect-video border border-neutral-800 w-44' />;
return (
<View
@@ -102,14 +105,16 @@ const ContinueWatchingPoster: React.FC<ContinueWatchingPosterProps> = ({
{progress > 0 && (
<>
<View
className={`absolute w-100 bottom-0 left-0 h-1 bg-neutral-700 opacity-80 w-full`}
></View>
className={
"absolute w-100 bottom-0 left-0 h-1 bg-neutral-700 opacity-80 w-full"
}
/>
<View
style={{
width: `${progress}%`,
}}
className={`absolute bottom-0 left-0 h-1 bg-purple-600 w-full`}
></View>
className={"absolute bottom-0 left-0 h-1 bg-purple-600 w-full"}
/>
</>
)}
</View>

View File

@@ -147,8 +147,7 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo(
</View>
}
logo={
<>
{logoUrl ? (
logoUrl ? (
<Image
source={{
uri: logoUrl,
@@ -161,8 +160,7 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo(
onLoad={() => setLoadingLogo(false)}
onError={() => setLoadingLogo(false)}
/>
) : null}
</>
) : null
}
>
<View className='flex flex-col bg-transparent shrink'>

View File

@@ -237,5 +237,5 @@ const formatFileSize = (bytes?: number | null) => {
const i = Number.parseInt(
Math.floor(Math.log(bytes) / Math.log(1024)).toString(),
);
return Math.round((bytes / Math.pow(1024, i)) * 100) / 100 + " " + sizes[i];
return `${Math.round((bytes / 1024 ** i) * 100) / 100} ${sizes[i]}`;
};

View File

@@ -63,9 +63,8 @@ export const MoreMoviesWithActor: React.FC<Props> = ({
const x = acc.find((item) => item.Id === current.Id);
if (!x) {
return acc.concat([current]);
} else {
return acc;
}
return acc;
}, [] as BaseItemDto[]) || [];
return uniqueItems;

View File

@@ -239,7 +239,7 @@ export const PlayButton: React.FC<Props> = ({
const derivedTargetWidth = useDerivedValue(() => {
if (!item || !item.RunTimeTicks) return 0;
const userData = item.UserData;
if (userData && userData.PlaybackPositionTicks) {
if (userData?.PlaybackPositionTicks) {
return userData.PlaybackPositionTicks > 0
? Math.max(
(userData.PlaybackPositionTicks / item.RunTimeTicks) * 100,
@@ -331,7 +331,7 @@ export const PlayButton: React.FC<Props> = ({
accessibilityLabel='Play button'
accessibilityHint='Tap to play the media'
onPress={onPress}
className={`relative`}
className={"relative"}
{...props}
>
<View className='absolute w-full h-full top-0 left-0 rounded-xl z-10 overflow-hidden'>

View File

@@ -85,7 +85,7 @@ export const PlayButton: React.FC<Props> = ({
const derivedTargetWidth = useDerivedValue(() => {
if (!item || !item.RunTimeTicks) return 0;
const userData = item.UserData;
if (userData && userData.PlaybackPositionTicks) {
if (userData?.PlaybackPositionTicks) {
return userData.PlaybackPositionTicks > 0
? Math.max(
(userData.PlaybackPositionTicks / item.RunTimeTicks) * 100,
@@ -176,7 +176,7 @@ export const PlayButton: React.FC<Props> = ({
accessibilityLabel='Play button'
accessibilityHint='Tap to play the media'
onPress={onPress}
className={`relative`}
className={"relative"}
{...props}
>
<View className='absolute w-full h-full top-0 left-0 rounded-xl z-10 overflow-hidden'>

View File

@@ -7,7 +7,7 @@ export const WatchedIndicator: React.FC<{ item: BaseItemDto }> = ({ item }) => {
<>
{item.UserData?.Played === false &&
(item.Type === "Movie" || item.Type === "Episode") && (
<View className='bg-purple-600 w-8 h-8 absolute -top-4 -right-4 rotate-45'></View>
<View className='bg-purple-600 w-8 h-8 absolute -top-4 -right-4 rotate-45' />
)}
</>
);

View File

@@ -3,7 +3,7 @@ import renderer from "react-test-renderer";
import { ThemedText } from "../ThemedText";
it(`renders correctly`, () => {
it("renders correctly", () => {
const tree = renderer
.create(<ThemedText>Snapshot test!</ThemedText>)
.toJSON();

View File

@@ -6,7 +6,7 @@ interface Props extends ViewProps {}
export const TitleHeader: React.FC<Props> = ({ ...props }) => {
return (
<View {...props}>
<Text></Text>
<Text />
</View>
);
};

View File

@@ -75,7 +75,7 @@ const Dropdown = <T,>({
multiple ? (
<DropdownMenu.CheckboxItem
value={
selected?.some((s) => keyExtractor(s) == keyExtractor(item))
selected?.some((s) => keyExtractor(s) === keyExtractor(item))
? "on"
: "off"
}
@@ -83,7 +83,7 @@ const Dropdown = <T,>({
onValueChange={(next: "on" | "off", previous: "on" | "off") => {
setSelected((p) => {
const prev = p || [];
if (next == "on") {
if (next === "on") {
return [...prev, item];
}
return [

View File

@@ -65,17 +65,13 @@ export const HorizontalScroll = forwardRef<
}: {
item: T;
index: number;
}) => (
<View className='mr-2'>
<React.Fragment>{renderItem(item, index)}</React.Fragment>
</View>
);
}) => <View className='mr-2'>{renderItem(item, index)}</View>;
if (!data || loading) {
return (
<View className='px-4 mb-2'>
<View className='bg-neutral-950 h-24 w-full rounded-md mb-2'></View>
<View className='bg-neutral-950 h-10 w-full rounded-md mb-1'></View>
<View className='bg-neutral-950 h-24 w-full rounded-md mb-2' />
<View className='bg-neutral-950 h-10 w-full rounded-md mb-1' />
</View>
);
}

View File

@@ -75,9 +75,8 @@ export function InfiniteHorizontalScroll({
if (accumulatedItems < totalItems) {
return lastPage?.Items?.length * pages.length;
} else {
return undefined;
}
return undefined;
},
initialPageParam: 0,
enabled: !!api && !!user?.Id,
@@ -118,9 +117,7 @@ export function InfiniteHorizontalScroll({
<FlashList
data={flatData}
renderItem={({ item, index }) => (
<View className='mr-2'>
<React.Fragment>{renderItem(item, index)}</React.Fragment>
</View>
<View className='mr-2'>{renderItem(item, index)}</View>
)}
estimatedItemSize={height}
horizontal

View File

@@ -36,7 +36,7 @@ export const ItemImage: FC<Props> = ({
const source = useMemo(() => {
if (!api) {
onError && onError();
onError?.();
return;
}
return getItemImage({

View File

@@ -5,7 +5,7 @@ export const LargePoster: React.FC<{ url?: string | null }> = ({ url }) => {
if (!url)
return (
<View className='p-4 rounded-xl overflow-hidden '>
<View className='w-full aspect-video rounded-xl overflow-hidden border border-neutral-800'></View>
<View className='w-full aspect-video rounded-xl overflow-hidden border border-neutral-800' />
</View>
);

View File

@@ -16,7 +16,7 @@ export function Text(
{...otherProps}
/>
);
else
return (
<UITextView
allowFontScaling={false}

View File

@@ -20,10 +20,10 @@ export const VerticalSkeleton: React.FC<Props> = ({ index, ...props }) => {
aspectRatio: "10/15",
}}
className='w-full bg-neutral-800 mb-2 rounded-lg'
></View>
<View className='h-2 bg-neutral-800 rounded-full mb-1'></View>
<View className='h-2 bg-neutral-800 rounded-full mb-1'></View>
<View className='h-2 bg-neutral-800 rounded-full mb-2 w-1/2'></View>
/>
<View className='h-2 bg-neutral-800 rounded-full mb-1' />
<View className='h-2 bg-neutral-800 rounded-full mb-1' />
<View className='h-2 bg-neutral-800 rounded-full mb-2 w-1/2' />
</View>
);
};

View File

@@ -80,8 +80,6 @@ const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
task.stop();
}
}
} catch (e) {
throw e;
} finally {
await removeProcess(id);
await queryClient.refetchQueries({ queryKey: ["jobs"] });
@@ -131,7 +129,7 @@ const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
? `${Math.max(5, process.progress)}%`
: "5%",
}}
></View>
/>
)}
<View className='px-3 py-1.5 flex flex-col w-full'>
<View className='flex flex-row items-center w-full'>

View File

@@ -31,7 +31,7 @@ export const SeriesCard: React.FC<{ items: BaseItemDto[] }> = ({ items }) => {
destructiveButtonIndex,
},
(selectedIndex) => {
if (selectedIndex == destructiveButtonIndex) {
if (selectedIndex === destructiveButtonIndex) {
deleteSeries();
}
},

View File

@@ -15,7 +15,7 @@ interface FilterButtonProps<T> extends ViewProps {
queryFn: (params: any) => Promise<any>;
searchFilter?: (item: T, query: string) => boolean;
renderItemLabel: (item: T) => React.ReactNode;
multiple?: boolean,
multiple?: boolean;
icon?: "filter" | "sort";
}

View File

@@ -183,15 +183,13 @@ export const FilterSheet = <T,>({
<TouchableOpacity
onPress={() => {
if (multiple) {
if (!values.includes(item))
set(values.concat(item))
else set(values.filter(v => v !== item))
if (!values.includes(item)) set(values.concat(item));
else set(values.filter((v) => v !== item));
setTimeout(() => {
setOpen(false);
}, 250);
}
else {
} else {
if (!values.includes(item)) {
set([item]);
setTimeout(() => {
@@ -214,7 +212,7 @@ export const FilterSheet = <T,>({
height: StyleSheet.hairlineWidth,
}}
className='h-1 divide-neutral-700 '
></View>
/>
</View>
))}
</View>

View File

@@ -63,7 +63,7 @@ export const ScrollingCollectionList: React.FC<Props> = ({
>
{[1, 2, 3].map((i) => (
<View className='w-44' key={i}>
<View className='bg-neutral-900 h-24 w-full rounded-md mb-1'></View>
<View className='bg-neutral-900 h-24 w-full rounded-md mb-1' />
<View className='rounded-md overflow-hidden mb-1 self-start'>
<Text
className='text-neutral-900 bg-neutral-900 rounded-md'

View File

@@ -34,10 +34,7 @@ export const Stepper: React.FC<StepperProps> = ({
<Text>-</Text>
</TouchableOpacity>
<Text
className={
"w-auto h-8 bg-neutral-800 py-2 px-1 flex items-center justify-center" +
(appendValue ? "first-letter:px-2" : "")
}
className={`w-auto h-8 bg-neutral-800 py-2 px-1 flex items-center justify-center${appendValue ? "first-letter:px-2" : ""}`}
>
{value}
{appendValue}

View File

@@ -52,7 +52,7 @@ export const JellyserrIndexPage: React.FC<Props> = ({
} = useReactNavigationQuery({
queryKey: ["search", "jellyseerr", "discoverSettings", searchQuery],
queryFn: async () => jellyseerrApi?.discoverSettings(),
enabled: !!jellyseerrApi && searchQuery.length == 0,
enabled: !!jellyseerrApi && searchQuery.length === 0,
});
const {
@@ -110,7 +110,7 @@ export const JellyserrIndexPage: React.FC<Props> = ({
(r) => r.mediaType === MediaType.MOVIE,
) as MovieResult[],
sortingType || [
(m) => m.title.toLowerCase() == searchQuery.toLowerCase(),
(m) => m.title.toLowerCase() === searchQuery.toLowerCase(),
],
order || "desc",
),
@@ -124,7 +124,7 @@ export const JellyserrIndexPage: React.FC<Props> = ({
(r) => r.mediaType === MediaType.TV,
) as TvResult[],
sortingType || [
(t) => t.name.toLowerCase() == searchQuery.toLowerCase(),
(t) => t.name.toLowerCase() === searchQuery.toLowerCase(),
],
order || "desc",
),
@@ -138,7 +138,7 @@ export const JellyserrIndexPage: React.FC<Props> = ({
(r) => r.mediaType === "person",
) as PersonResult[],
sortingType || [
(p) => p.name.toLowerCase() == searchQuery.toLowerCase(),
(p) => p.name.toLowerCase() === searchQuery.toLowerCase(),
],
order || "desc",
),

View File

@@ -62,7 +62,7 @@ const JellyseerrStatusIcon: React.FC<Props & ViewProps> = ({
return (
badgeIcon && (
<TouchableOpacity onPress={onPress} disabled={onPress == undefined}>
<TouchableOpacity onPress={onPress} disabled={onPress === undefined}>
<View
className={`${badgeStyle ?? "bg-purple-600"} rounded-full h-6 w-6 flex items-center justify-center ${props.className}`}
{...props}

View File

@@ -123,11 +123,9 @@ const ParallaxSlideShow = <T,>({
>
<View className='flex flex-col space-y-4 px-4'>
<View className='flex flex-row justify-between w-full'>
<View className='flex flex-col w-full'>
{HeaderContent && HeaderContent()}
<View className='flex flex-col w-full'>{HeaderContent?.()}</View>
</View>
</View>
{MainContent && MainContent()}
{MainContent?.()}
<View>
<FlashList
data={data}

View File

@@ -61,7 +61,7 @@ const RequestModal = forwardRef<
const { data: serviceSettings } = useQuery({
queryKey: ["jellyseerr", "request", type, "service"],
queryFn: async () =>
jellyseerrApi?.service(type == "movie" ? "radarr" : "sonarr"),
jellyseerrApi?.service(type === "movie" ? "radarr" : "sonarr"),
enabled: !!jellyseerrApi && !!jellyseerrUser,
refetchOnMount: "always",
});

View File

@@ -28,7 +28,7 @@ const GenreSlide: React.FC<SlideProps & ViewProps> = ({ slide, ...props }) => {
queryKey: ["jellyseerr", "discover", slide.type, slide.id],
queryFn: async () => {
return jellyseerrApi?.getGenreSliders(
slide.type == DiscoverSliderType.MOVIE_GENRES
slide.type === DiscoverSliderType.MOVIE_GENRES
? Endpoints.MOVIE
: Endpoints.TV,
);

View File

@@ -23,7 +23,7 @@ const RequestCard: React.FC<{ request: MediaRequest }> = ({ request }) => {
request.media.tmdbId,
],
queryFn: async () => {
return request.media.mediaType == MediaType.MOVIE
return request.media.mediaType === MediaType.MOVIE
? jellyseerrApi?.movieDetails(request.media.tmdbId)
: jellyseerrApi?.tvDetails(request.media.tmdbId);
},

View File

@@ -35,7 +35,7 @@ const Slide = <T,>({
return (
<View {...props}>
<Text className='font-bold text-lg mb-2 px-4'>
{t("search." + DiscoverSliderType[slide.type].toString().toLowerCase())}
{t(`search.${DiscoverSliderType[slide.type].toString().toLowerCase()}`)}
</Text>
<FlashList
horizontal

View File

@@ -29,8 +29,8 @@ export const EpisodePoster: React.FC<MoviePosterProps> = ({
);
const blurhash = useMemo(() => {
const key = item.ImageTags?.["Primary"] as string;
return item.ImageBlurHashes?.["Primary"]?.[key];
const key = item.ImageTags?.Primary as string;
return item.ImageBlurHashes?.Primary?.[key];
}, [item]);
return (
@@ -57,7 +57,7 @@ export const EpisodePoster: React.FC<MoviePosterProps> = ({
/>
<WatchedIndicator item={item} />
{showProgress && progress > 0 && (
<View className='h-1 bg-red-600 w-full'></View>
<View className='h-1 bg-red-600 w-full' />
)}
</View>
);

View File

@@ -37,7 +37,7 @@ export const ItemPoster: React.FC<Props> = ({
/>
<WatchedIndicator item={item} />
{showProgress && progress > 0 && (
<View className='h-1 bg-red-600 w-full'></View>
<View className='h-1 bg-red-600 w-full' />
)}
</View>
);

View File

@@ -129,7 +129,7 @@ const JellyseerrPoster: React.FC<Props> = ({
posterSrc={posterSrc!}
mediaType={mediaType}
>
<View className={`flex flex-col mr-2 h-auto`}>
<View className={"flex flex-col mr-2 h-auto"}>
<View
className={`relative rounded-lg overflow-hidden border border-neutral-900 ${size} aspect-[${ratio}]`}
>

View File

@@ -31,8 +31,8 @@ const MoviePoster: React.FC<MoviePosterProps> = ({
);
const blurhash = useMemo(() => {
const key = item.ImageTags?.["Primary"] as string;
return item.ImageBlurHashes?.["Primary"]?.[key];
const key = item.ImageTags?.Primary as string;
return item.ImageBlurHashes?.Primary?.[key];
}, [item]);
return (
@@ -59,7 +59,7 @@ const MoviePoster: React.FC<MoviePosterProps> = ({
/>
<WatchedIndicator item={item} />
{showProgress && progress > 0 && (
<View className='h-1 bg-red-600 w-full'></View>
<View className='h-1 bg-red-600 w-full' />
)}
</View>
);

View File

@@ -24,7 +24,7 @@ const ParentPoster: React.FC<PosterProps> = ({ id }) => {
style={{
aspectRatio: "10/15",
}}
></View>
/>
);
return (

View File

@@ -16,7 +16,7 @@ const Poster: React.FC<PosterProps> = ({ id, url, blurhash }) => {
style={{
aspectRatio: "10/15",
}}
></View>
/>
);
return (

View File

@@ -27,8 +27,8 @@ const SeriesPoster: React.FC<MoviePosterProps> = ({ item }) => {
}, [item]);
const blurhash = useMemo(() => {
const key = item.ImageTags?.["Primary"] as string;
return item.ImageBlurHashes?.["Primary"]?.[key];
const key = item.ImageTags?.Primary as string;
return item.ImageBlurHashes?.Primary?.[key];
}, [item]);
return (

View File

@@ -35,11 +35,11 @@ export const LoadingSkeleton: React.FC<Props> = ({ isLoading }) => {
<Animated.View style={animatedStyle} className='mt-2 absolute w-full'>
{[1, 2, 3].map((s) => (
<View className='px-4 mb-4' key={s}>
<View className='w-1/2 bg-neutral-900 h-6 mb-2 rounded-lg'></View>
<View className='w-1/2 bg-neutral-900 h-6 mb-2 rounded-lg' />
<View className='flex flex-row gap-2'>
{[1, 2, 3].map((i) => (
<View className='w-28' key={i}>
<View className='bg-neutral-900 h-40 w-full rounded-md mb-1'></View>
<View className='bg-neutral-900 h-40 w-full rounded-md mb-1' />
<View className='rounded-md overflow-hidden mb-1 self-start'>
<Text
className='text-neutral-900 bg-neutral-900 rounded-md'

View File

@@ -57,7 +57,7 @@ export const NextItemButton: React.FC<Props> = ({
return (
<Button
onPress={() => router.setParams({ id: nextItem?.Id })}
className={`h-12 aspect-square`}
className={"h-12 aspect-square"}
disabled={disabled}
{...props}
>

View File

@@ -146,7 +146,7 @@ export const SeasonPicker: React.FC<Props> = ({ item, initialSeasonIndex }) => {
}));
}}
/>
{episodes?.length || 0 > 0 ? (
{episodes?.length ? (
<View className='flex flex-row items-center space-x-2'>
<DownloadItems
title={t("item_card.download.download_season")}

View File

@@ -19,7 +19,7 @@ export const Dashboard = () => {
<View>
<ListGroup title={t("home.settings.dashboard.title")} className='mt-4'>
<ListItem
className={sessions.length != 0 ? "bg-purple-900" : ""}
className={sessions.length !== 0 ? "bg-purple-900" : ""}
onPress={() => router.push("/settings/dashboard/sessions")}
title={t("home.settings.dashboard.sessions_title")}
showArrow

View File

@@ -140,7 +140,7 @@ export default function DownloadSettings({ ...props }) {
onPress={() => router.push("/settings/optimized-server/page")}
showArrow
title={t("home.settings.downloads.optimized_versions_server")}
></ListItem>
/>
</ListGroup>
</DisabledSetting>
);

View File

@@ -51,13 +51,13 @@ type ScrollingCollectionListSection = {
orientation?: "horizontal" | "vertical";
};
type MediaListSection = {
type MediaListSectionType = {
type: "MediaListSection";
queryKey: (string | undefined)[];
queryFn: QueryFunction<BaseItemDto>;
};
type Section = ScrollingCollectionListSection | MediaListSection;
type Section = ScrollingCollectionListSection | MediaListSectionType;
export const HomeIndex = () => {
const router = useRouter();
@@ -133,7 +133,7 @@ export const HomeIndex = () => {
useEffect(() => {
const unsubscribe = NetInfo.addEventListener((state) => {
if (state.isConnected == false || state.isInternetReachable === false)
if (state.isConnected === false || state.isInternetReachable === false)
setIsConnected(false);
else setIsConnected(true);
});
@@ -236,7 +236,7 @@ export const HomeIndex = () => {
const title = t("home.recently_added_in", { libraryName: c.Name });
const queryKey = [
"home",
"recentlyAddedIn" + c.CollectionType,
`recentlyAddedIn${c.CollectionType}`,
user?.Id!,
c.Id!,
];
@@ -353,7 +353,8 @@ export const HomeIndex = () => {
parentId: section.items?.parentId,
});
return response.data.Items || [];
} else if (section.nextUp) {
}
if (section.nextUp) {
const response = await getTvShowsApi(api).getNextUp({
userId: user?.Id,
fields: ["MediaSourceCount"],
@@ -363,7 +364,9 @@ export const HomeIndex = () => {
enableRewatching: section.items?.enableRewatching,
});
return response.data.Items || [];
} else if (section.latest) {
}
if (section.latest) {
const response = await getUserLibraryApi(api).getLatestMedia({
userId: user?.Id,
includeItemTypes: section.latest?.includeItemTypes,
@@ -472,7 +475,8 @@ export const HomeIndex = () => {
hideIfEmpty
/>
);
} else if (section.type === "MediaListSection") {
}
if (section.type === "MediaListSection") {
return (
<MediaListSection
key={index}

View File

@@ -20,7 +20,7 @@ export const OptimizedServerForm: React.FC<Props> = ({
return (
<View>
<View className='flex flex-col rounded-xl overflow-hidden pl-4 bg-neutral-900 px-4'>
<View className={`flex flex-row items-center bg-neutral-900 h-11 pr-4`}>
<View className={"flex flex-row items-center bg-neutral-900 h-11 pr-4"}>
<Text className='mr-4'>{t("home.settings.downloads.url")}</Text>
<TextInput
className='text-white'

View File

@@ -81,7 +81,7 @@ export const StorageSettings = () => {
{size && (
<>
<View className='flex flex-row items-center'>
<View className='w-3 h-3 rounded-full bg-purple-600 mr-1'></View>
<View className='w-3 h-3 rounded-full bg-purple-600 mr-1' />
<Text className='text-white text-xs'>
{t("home.settings.storage.app_usage", {
usedSpace: calculatePercentage(size.app, size.total),
@@ -89,7 +89,7 @@ export const StorageSettings = () => {
</Text>
</View>
<View className='flex flex-row items-center'>
<View className='w-3 h-3 rounded-full bg-purple-400 mr-1'></View>
<View className='w-3 h-3 rounded-full bg-purple-400 mr-1' />
<Text className='text-white text-xs'>
{t("home.settings.storage.device_usage", {
availableSpace: calculatePercentage(

View File

@@ -168,7 +168,7 @@ export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
style={{
justifyContent: "space-between",
}}
className={`flex flex-row items-center space-x-2 z-10 p-4`}
className={"flex flex-row items-center space-x-2 z-10 p-4"}
>
{seriesItem && (
<SeasonDropdown

View File

@@ -47,7 +47,7 @@ const SliderScrubber: React.FC<SliderScrubberProps> = ({
};
return (
<View className={`flex flex-col w-full shrink`}>
<View className={"flex flex-col w-full shrink"}>
<Slider
theme={{
maximumTrackTintColor: "rgba(255,255,255,0.2)",

View File

@@ -115,7 +115,7 @@ export const VideoProvider: React.FC<VideoProviderProps> = ({
});
return;
}
setTrack && setTrack(index);
setTrack?.(index);
router.setParams({
[paramKey]: serverIndex.toString(),
});

View File

@@ -32,8 +32,8 @@ export const useTapDetection = ({
const touchDuration = touchEndTime - touchStartTime.current;
const touchDistance = Math.sqrt(
Math.pow(touchEndPosition.x - touchStartPosition.current.x, 2) +
Math.pow(touchEndPosition.y - touchStartPosition.current.y, 2),
(touchEndPosition.x - touchStartPosition.current.x) ** 2 +
(touchEndPosition.y - touchStartPosition.current.y) ** 2,
);
if (touchDuration < maxDuration && touchDistance < maxDistance) {

View File

@@ -45,15 +45,13 @@ export const VideoDebugInfo: React.FC<Props> = ({ playerRef, ...props }) => {
>
<Text className='font-bold'>{t("player.playback_state")}</Text>
<Text className='font-bold mt-2.5'>{t("player.audio_tracks")}</Text>
{audioTracks &&
audioTracks.map((track, index) => (
{audioTracks?.map((track, index) => (
<Text key={index}>
{track.name} ({t("player.index")} {track.index})
</Text>
))}
<Text className='font-bold mt-2.5'>{t("player.subtitles_tracks")}</Text>
{subtitleTracks &&
subtitleTracks.map((track, index) => (
{subtitleTracks?.map((track, index) => (
<Text key={index}>
{track.name} ({t("player.index")} {track.index})
</Text>

View File

@@ -35,7 +35,7 @@ export const useDownloadedFileOpener = () => {
async (item: BaseItemDto) => {
try {
// @ts-expect-error
router.push("/player/direct-player?offline=true&itemId=" + item.Id);
router.push(`/player/direct-player?offline=true&itemId=${item.Id}`);
} catch (error) {
writeToLog("ERROR", "Error opening file", error);
console.error("Error opening file:", error);

View File

@@ -38,7 +38,7 @@ export const useImageColors = ({
const source = useMemo(() => {
if (!api) return;
if (url) return { uri: url };
else if (item)
if (item)
return getItemImage({
item,
api,
@@ -46,7 +46,7 @@ export const useImageColors = ({
quality: 80,
width: 300,
});
else return null;
return null;
}, [api, item]);
useEffect(() => {

View File

@@ -87,11 +87,11 @@ export enum Endpoints {
STUDIO = "/studio",
GENRE_SLIDER = "/genreslider",
DISCOVER = "/discover",
DISCOVER_TRENDING = DISCOVER + "/trending",
DISCOVER_MOVIES = DISCOVER + "/movies",
DISCOVER_TRENDING = `${DISCOVER}/trending`,
DISCOVER_MOVIES = `${DISCOVER}/movies`,
DISCOVER_TV = DISCOVER + TV,
DISCOVER_TV_NETWORK = DISCOVER + TV + NETWORK,
DISCOVER_MOVIES_STUDIO = DISCOVER + `${MOVIE}s` + STUDIO,
DISCOVER_MOVIES_STUDIO = `${DISCOVER}${MOVIE}s${STUDIO}`,
AUTH_JELLYFIN = "/auth/jellyfin",
}
@@ -159,9 +159,8 @@ export class JellyseerrApi {
}
toast.error(t("jellyseerr.toasts.jellyseerr_test_failed"));
writeErrorLog(
`Jellyseerr returned a ${status} for url:\n` +
response.config.url ,
response.data
`Jellyseerr returned a ${status} for url:\n${response.config.url}`,
response.data,
);
return {
isValid: false,
@@ -240,7 +239,7 @@ export class JellyseerrApi {
async getRequest(id: number): Promise<MediaRequest> {
return this.axios
?.get<MediaRequest>(Endpoints.API_V1 + Endpoints.REQUEST + `/${id}`)
?.get<MediaRequest>(`${Endpoints.API_V1 + Endpoints.REQUEST}/${id}`)
.then(({ data }) => data);
}
@@ -261,7 +260,7 @@ export class JellyseerrApi {
async movieDetails(id: number) {
return this.axios
?.get<MovieDetails>(Endpoints.API_V1 + Endpoints.MOVIE + `/${id}`)
?.get<MovieDetails>(`${Endpoints.API_V1 + Endpoints.MOVIE}/${id}`)
.then((response) => {
return response?.data;
});
@@ -269,7 +268,7 @@ export class JellyseerrApi {
async personDetails(id: number | string): Promise<PersonDetails> {
return this.axios
?.get<PersonDetails>(Endpoints.API_V1 + Endpoints.PERSON + `/${id}`)
?.get<PersonDetails>(`${Endpoints.API_V1 + Endpoints.PERSON}/${id}`)
.then((response) => {
return response?.data;
});
@@ -278,10 +277,9 @@ export class JellyseerrApi {
async personCombinedCredits(id: number | string): Promise<CombinedCredit> {
return this.axios
?.get<CombinedCredit>(
Endpoints.API_V1 +
Endpoints.PERSON +
`/${id}` +
Endpoints.COMBINED_CREDITS,
`${
Endpoints.API_V1 + Endpoints.PERSON
}/${id}${Endpoints.COMBINED_CREDITS}`,
)
.then((response) => {
return response?.data;
@@ -332,13 +330,10 @@ export class JellyseerrApi {
imageProxy(path?: string, filter = "original", width = 1920, quality = 75) {
return path
? this.axios.defaults.baseURL +
`/_next/image?` +
new URLSearchParams(
? `${this.axios.defaults.baseURL}/_next/image?${new URLSearchParams(
`url=https://image.tmdb.org/t/p/${filter}/${path}&w=${width}&q=${quality}`,
).toString()
: this.axios?.defaults.baseURL +
`/images/overseerr_poster_not_found_logo_top.png`;
).toString()}`
: `${this.axios?.defaults.baseURL}/images/overseerr_poster_not_found_logo_top.png`;
}
async submitIssue(mediaId: number, issueType: IssueType, message: string) {
@@ -361,7 +356,7 @@ export class JellyseerrApi {
async service(type: "radarr" | "sonarr") {
return this.axios
?.get<ServiceCommonServer[]>(
Endpoints.API_V1 + Endpoints.SERVICE + `/${type}`,
`${Endpoints.API_V1 + Endpoints.SERVICE}/${type}`,
)
.then(({ data }) => data);
}
@@ -369,7 +364,7 @@ export class JellyseerrApi {
async serviceDetails(type: "radarr" | "sonarr", id: number) {
return this.axios
?.get<ServiceCommonServerWithDetails>(
Endpoints.API_V1 + Endpoints.SERVICE + `/${type}` + `/${id}`,
`${Endpoints.API_V1 + Endpoints.SERVICE}/${type}/${id}`,
)
.then(({ data }) => data);
}
@@ -388,9 +383,7 @@ export class JellyseerrApi {
},
(error: AxiosError) => {
writeErrorLog(
"Jellyseerr response error\n" +
`error: ${error.toString()}\n` +
`url: ${error?.config?.url}`,
`Jellyseerr response error\nerror: ${error.toString()}\nurl: ${error?.config?.url}`,
error.response?.data,
);
if (error.status === 403) {
@@ -407,7 +400,7 @@ export class JellyseerrApi {
const headerName = this.axios.defaults.xsrfHeaderName!;
const xsrfToken = cookies
.find((c) => c.includes(headerName))
?.split(headerName + "=")?.[1];
?.split(`${headerName}=`)?.[1];
if (xsrfToken) {
config.headers[headerName] = xsrfToken;
}
@@ -479,7 +472,7 @@ export const useJellyseerr = () => {
return (
items &&
Object.hasOwn(items, "mediaType") &&
Object.values(MediaType).includes(items["mediaType"])
Object.values(MediaType).includes(items.mediaType)
);
};
@@ -487,10 +480,10 @@ export const useJellyseerr = () => {
item?: TvResult | TvDetails | MovieResult | MovieDetails,
) => {
return isJellyseerrResult(item)
? item.mediaType == MediaType.MOVIE
? item.mediaType === MediaType.MOVIE
? item?.title
: item?.name
: item?.mediaInfo.mediaType == MediaType.MOVIE
: item?.mediaInfo.mediaType === MediaType.MOVIE
? (item as MovieDetails)?.title
: (item as TvDetails)?.name;
};
@@ -500,10 +493,10 @@ export const useJellyseerr = () => {
) => {
return new Date(
(isJellyseerrResult(item)
? item.mediaType == MediaType.MOVIE
? item.mediaType === MediaType.MOVIE
? item?.releaseDate
: item?.firstAirDate
: item?.mediaInfo.mediaType == MediaType.MOVIE
: item?.mediaInfo.mediaType === MediaType.MOVIE
? (item as MovieDetails)?.releaseDate
: (item as TvDetails)?.firstAirDate) || "",
)?.getFullYear?.();

View File

@@ -1,3 +1,5 @@
import { ViewStyle } from "react-native";
export type PlaybackStatePayload = {
nativeEvent: {
target: number;
@@ -59,7 +61,7 @@ export type ChapterInfo = {
export type VlcPlayerViewProps = {
source: VlcPlayerSource;
style?: Record<string, unknown>;
style?: ViewStyle | ViewStyle[];
progressUpdateInterval?: number;
paused?: boolean;
muted?: boolean;

View File

@@ -2,7 +2,7 @@ import { requireNativeViewManager } from "expo-modules-core";
import * as React from "react";
import { VideoPlayer, useSettings } from "@/utils/atoms/settings";
import { Platform } from "react-native";
import { Platform, ViewStyle } from "react-native";
import type {
VlcPlayerSource,
VlcPlayerViewProps,
@@ -22,7 +22,7 @@ const NativeView = React.forwardRef<NativeViewRef, VlcPlayerViewProps>(
const [settings] = useSettings();
if (Platform.OS === "ios" || Platform.isTVOS) {
if (settings.defaultPlayer == VideoPlayer.VLC_3) {
if (settings.defaultPlayer === VideoPlayer.VLC_3) {
console.log("[Apple] Using Vlc Player 3");
return <VLC3ViewManager {...props} ref={ref} />;
}
@@ -118,7 +118,9 @@ const VlcPlayerView = React.forwardRef<VlcPlayerViewRef, VlcPlayerViewProps>(
} = props;
const processedSource: VlcPlayerSource =
typeof source === "string" ? { uri: source } : source;
typeof source === "string"
? ({ uri: source } as unknown as VlcPlayerSource)
: source;
if (processedSource.startPosition !== undefined) {
processedSource.startPosition = Math.floor(processedSource.startPosition);
@@ -129,7 +131,7 @@ const VlcPlayerView = React.forwardRef<VlcPlayerViewRef, VlcPlayerViewProps>(
{...otherProps}
ref={nativeRef}
source={processedSource}
style={[{ width: "100%", height: "100%" }, style]}
style={[{ width: "100%", height: "100%" }, style as ViewStyle]}
progressUpdateInterval={progressUpdateInterval}
paused={paused}
muted={muted}

View File

@@ -13,7 +13,8 @@
"prebuild": "EXPO_TV=0 bun run clean",
"prebuild:tv": "EXPO_TV=1 bun run clean",
"prepare": "husky",
"lint": "biome format --write ."
"check": "biome check .",
"lint": "biome check --write --unsafe"
},
"dependencies": {
"@bottom-tabs/react-navigation": "0.8.6",
@@ -134,6 +135,7 @@
}
},
"lint-staged": {
"*": ["biome check --no-errors-on-unmatched --files-ignore-unknown=true"]
"*.{js,jsx,ts,tsx}": ["biome check --write --unsafe"],
"*.{json,md}": ["biome format --write"]
}
}

View File

@@ -1,5 +1,5 @@
const { readFileSync, writeFileSync } = require("fs");
const { join } = require("path");
const { readFileSync, writeFileSync } = require("node:fs");
const { join } = require("node:path");
const { withDangerousMod } = require("@expo/config-plugins");
const withChangeNativeAndroidTextToWhite = (expoConfig) =>

View File

@@ -1,7 +1,7 @@
const { AndroidConfig, withAndroidManifest } = require("@expo/config-plugins");
const { Paths } = require("@expo/config-plugins/build/android");
const path = require("path");
const fs = require("fs");
const path = require("node:path");
const fs = require("node:fs");
const fsPromises = fs.promises;
const { getMainApplicationOrThrow } = AndroidConfig.Manifest;

View File

@@ -63,7 +63,7 @@ function useDownloadProvider() {
function saveDownloadedItemInfo(item: BaseItemDto, size = 0) {}
function getDownloadedItemSize(itemId: string): number {
const size = storage.getString("downloadedItemSize-" + itemId);
const size = storage.getString(`downloadedItemSize-${itemId}`);
return size ? Number.parseInt(size) : 0;
}

View File

@@ -101,7 +101,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
if (!api || !deviceId) return;
try {
const response = await api.axiosInstance.post(
api.basePath + "/QuickConnect/Initiate",
`${api.basePath}/QuickConnect/Initiate`,
null,
{
headers,
@@ -111,9 +111,8 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
setSecret(response?.data?.Secret);
setIsPolling(true);
return response.data?.Code;
} else {
throw new Error("Failed to initiate quick connect");
}
throw new Error("Failed to initiate quick connect");
} catch (error) {
console.error(error);
throw error;
@@ -133,7 +132,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
setIsPolling(false);
const authResponse = await api.axiosInstance.post(
api.basePath + "/Users/AuthenticateWithQuickConnect",
`${api.basePath}/Users/AuthenticateWithQuickConnect`,
{
secret,
},
@@ -156,11 +155,10 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
setIsPolling(false);
setSecret(null);
throw new Error("The code has expired. Please try again.");
} else {
}
console.error("Error polling Quick Connect:", error);
throw error;
}
}
}, [api, secret, headers]);
useEffect(() => {
@@ -290,7 +288,9 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
api
?.delete(`/Streamyfin/device/${deviceId}`)
.then((r) => writeInfoLog("Deleted expo push token for device"))
.catch((e) => writeErrorLog("Failed to delete expo push token for device"));
.catch((e) =>
writeErrorLog("Failed to delete expo push token for device"),
);
storage.delete("token");
setUser(null);

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const process = require("process");
const { execSync } = require("child_process");
const fs = require("node:fs");
const path = require("node:path");
const process = require("node:process");
const { execSync } = require("node:child_process");
const root = process.cwd();
// const tvosPath = path.join(root, 'iostv');

View File

@@ -15,7 +15,6 @@ function orientationToOrientationLock(
return OrientationLock.LANDSCAPE_LEFT;
case Orientation.LANDSCAPE_RIGHT:
return OrientationLock.LANDSCAPE_RIGHT;
case Orientation.UNKNOWN:
default:
return OrientationLock.DEFAULT;
}

View File

@@ -27,8 +27,8 @@ export const useJellyseerrCanRequest = (
const canNotRequest =
item?.mediaInfo?.requests?.some(
(r: MediaRequest) =>
r.status == MediaRequestStatus.PENDING ||
r.status == MediaRequestStatus.APPROVED,
r.status === MediaRequestStatus.PENDING ||
r.status === MediaRequestStatus.APPROVED,
) ||
item.mediaInfo?.status === MediaStatus.AVAILABLE ||
item.mediaInfo?.status === MediaStatus.BLACKLISTED ||

View File

@@ -41,7 +41,7 @@ const calculateContrastRatio = (rgb1: number[], rgb2: number[]): number => {
const calculateRelativeLuminance = (rgb: number[]): number => {
const [r, g, b] = rgb.map((c) => {
c /= 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
});
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};

View File

@@ -11,9 +11,8 @@ export function convertBitsToMegabitsOrGigabits(bits?: number | null): string {
const megabits = bits / 1000000;
if (megabits < 1000) {
return Math.round(megabits) + "MB";
} else {
const gigabits = megabits / 1000;
return gigabits.toFixed(1) + "GB";
return `${Math.round(megabits)}MB`;
}
const gigabits = megabits / 1000;
return `${gigabits.toFixed(1)}GB`;
}

View File

@@ -6,5 +6,5 @@ export const formatBitrate = (bitrate?: number | null) => {
const i = Number.parseInt(
Math.floor(Math.log(bitrate) / Math.log(1000)).toString(),
);
return Math.round((bitrate / Math.pow(1000, i)) * 100) / 100 + " " + sizes[i];
return `${Math.round((bitrate / 1000 ** i) * 100) / 100} ${sizes[i]}`;
};

View File

@@ -41,8 +41,6 @@ export const colletionTypeToItemType = (
return BaseItemKind.Photo;
case CollectionType.Trailers:
return BaseItemKind.Trailer;
case CollectionType.Playlists:
return BaseItemKind.Playlist;
}
return undefined;

View File

@@ -54,7 +54,7 @@ export const getItemImage = ({
};
break;
case "Primary":
tag = item.ImageTags?.["Primary"];
tag = item.ImageTags?.Primary;
if (!tag) break;
blurhash = item.ImageBlurHashes?.Primary?.[tag];
@@ -64,7 +64,7 @@ export const getItemImage = ({
};
break;
case "Thumb":
tag = item.ImageTags?.["Thumb"];
tag = item.ImageTags?.Thumb;
if (!tag) break;
blurhash = item.ImageBlurHashes?.Thumb?.[tag];
@@ -74,7 +74,7 @@ export const getItemImage = ({
};
break;
default:
tag = item.ImageTags?.["Primary"];
tag = item.ImageTags?.Primary;
src = {
uri: `${api.basePath}/Items/${item.Id}/Images/Primary?quality=${quality}&tag=${tag}&width=${width}`,
};

View File

@@ -24,12 +24,12 @@ export async function parseM3U8ForSubtitles(
const attributes = parseAttributes(line);
const track: SubtitleTrack = {
index: index++,
name: attributes["NAME"] || "",
uri: attributes["URI"] || "",
language: attributes["LANGUAGE"] || "",
default: attributes["DEFAULT"] === "YES",
forced: attributes["FORCED"] === "YES",
autoSelect: attributes["AUTOSELECT"] === "YES",
name: attributes.NAME || "",
uri: attributes.URI || "",
language: attributes.LANGUAGE || "",
default: attributes.DEFAULT === "YES",
forced: attributes.FORCED === "YES",
autoSelect: attributes.AUTOSELECT === "YES",
};
subtitleTracks.push(track);
}

View File

@@ -45,7 +45,6 @@ export const getBackdropUrl = ({
return `${api.basePath}/Items/${
item.Id
}/Images/Backdrop/0?${params.toString()}`;
} else {
return getPrimaryImageUrl({ api, item, quality, width });
}
return getPrimaryImageUrl({ api, item, quality, width });
};

View File

@@ -39,7 +39,7 @@ export const getLogoImageUrlById = ({
return `${api.basePath}/Items/${parentId}/Images/Logo?${params.toString()}`;
}
const imageTag = item.ImageTags?.["Logo"];
const imageTag = item.ImageTags?.Logo;
if (!imageTag) return null;

View File

@@ -31,7 +31,7 @@ export const getPrimaryImageUrl = ({
return `${api?.basePath}/Items/${item?.Id}/Images/Primary`;
}
const primaryTag = item.ImageTags?.["Primary"];
const primaryTag = item.ImageTags?.Primary;
const backdropTag = item.BackdropImageTags?.[0];
const parentBackdropTag = item.ParentBackdropImageTags?.[0];

Some files were not shown because too many files have changed in this diff Show More