diff options
Diffstat (limited to 'frontend/src/pages')
18 files changed, 261 insertions, 73 deletions
diff --git a/frontend/src/pages/Movies/index.tsx b/frontend/src/pages/Movies/index.tsx index 0429e1fdd..ef5a1ec0d 100644 --- a/frontend/src/pages/Movies/index.tsx +++ b/frontend/src/pages/Movies/index.tsx @@ -6,6 +6,7 @@ import { faBookmark as farBookmark } from "@fortawesome/free-regular-svg-icons"; import { faBookmark, faWrench } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { ColumnDef } from "@tanstack/react-table"; +import { uniqueId } from "lodash"; import { useMovieModification, useMoviesPagination } from "@/apis/hooks"; import { Action } from "@/components"; import { AudioList } from "@/components/bazarr"; @@ -95,7 +96,7 @@ const MovieView: FunctionComponent = () => { <Badge mr="xs" color="yellow" - key={BuildKey(v.code2, v.hi, v.forced)} + key={uniqueId(`${BuildKey(v.code2, v.hi, v.forced)}_`)} > <Language.Text value={v}></Language.Text> </Badge> diff --git a/frontend/src/pages/Series/index.tsx b/frontend/src/pages/Series/index.tsx index 229082444..c142a6767 100644 --- a/frontend/src/pages/Series/index.tsx +++ b/frontend/src/pages/Series/index.tsx @@ -65,25 +65,34 @@ const SeriesView: FunctionComponent = () => { cell: (row) => { const { episodeFileCount, episodeMissingCount, profileId, title } = row.row.original; - let progress = 0; - let label = ""; - if (episodeFileCount === 0 || !profileId) { - progress = 0.0; - } else { - progress = (1.0 - episodeMissingCount / episodeFileCount) * 100.0; - label = `${ - episodeFileCount - episodeMissingCount - }/${episodeFileCount}`; - } + const label = `${episodeFileCount - episodeMissingCount}/${episodeFileCount}`; return ( <Progress.Root key={title} size="xl"> <Progress.Section - value={progress} + value={ + episodeFileCount === 0 || !profileId + ? 0 + : (1.0 - episodeMissingCount / episodeFileCount) * 100.0 + } color={episodeMissingCount === 0 ? "brand" : "yellow"} > <Progress.Label>{label}</Progress.Label> </Progress.Section> + {episodeMissingCount === episodeFileCount && ( + <Progress.Label + styles={{ + label: { + position: "absolute", + top: "3px", + left: "50%", + transform: "translateX(-50%)", + }, + }} + > + {label} + </Progress.Label> + )} </Progress.Root> ); }, diff --git a/frontend/src/pages/Settings/General/index.tsx b/frontend/src/pages/Settings/General/index.tsx index 312d09d1f..6db3ee7fc 100644 --- a/frontend/src/pages/Settings/General/index.tsx +++ b/frontend/src/pages/Settings/General/index.tsx @@ -43,10 +43,10 @@ const SettingsGeneralView: FunctionComponent = () => { <Section header="Host"> <Text label="Address" - placeholder="0.0.0.0" + placeholder="*" settingKey="settings-general-ip" ></Text> - <Message>Valid IPv4 address or '0.0.0.0' for all interfaces</Message> + <Message>Valid IP address or '*' for all interfaces</Message> <Number label="Port" placeholder="6767" diff --git a/frontend/src/pages/Settings/Languages/index.tsx b/frontend/src/pages/Settings/Languages/index.tsx index 9fe562920..1bd9d72a8 100644 --- a/frontend/src/pages/Settings/Languages/index.tsx +++ b/frontend/src/pages/Settings/Languages/index.tsx @@ -1,7 +1,9 @@ import { FunctionComponent } from "react"; +import { Text as MantineText } from "@mantine/core"; import { useLanguageProfiles, useLanguages } from "@/apis/hooks"; import { Check, + Chips, CollapseBox, Layout, Message, @@ -115,6 +117,50 @@ const SettingsLanguagesView: FunctionComponent = () => { <Section header="Languages Profile"> <Table></Table> </Section> + <Section header="Tag-Based Automatic Language Profile Selection Settings"> + <Message> + If enabled, Bazarr will look at the names of all tags of a Series from + Sonarr (or a Movie from Radarr) to find a matching Bazarr language + profile tag. It will use as the language profile the FIRST tag from + Sonarr/Radarr that matches the tag of a Bazarr language profile + EXACTLY. If multiple tags match, there is no guarantee as to which one + will be used, so choose your tag names carefully. Also, if you update + the tag names in Sonarr/Radarr, Bazarr will detect this and repeat the + matching process for the affected shows. However, if a show's only + matching tag is removed from Sonarr/Radarr, Bazarr will NOT remove the + show's existing language profile for that reason. But if you wish to + have language profiles removed automatically by tag value, simply + enter a list of one or more tags in the{" "} + <MantineText fw={700} span> + Remove Profile Tags + </MantineText>{" "} + entry list below. If your video tag matches one of the tags in that + list, then Bazarr will remove the language profile for that video. If + there is a conflict between profile selection and profile removal, + then profile removal wins out and is performed. + </Message> + <Check + label="Series" + settingKey="settings-general-serie_tag_enabled" + ></Check> + <Check + label="Movies" + settingKey="settings-general-movie_tag_enabled" + ></Check> + <Chips + label="Remove Profile Tags" + settingKey="settings-general-remove_profile_tags" + sanitizeFn={(values: string[] | null) => + values?.map((item) => + item.replace(/[^a-z0-9_-]/gi, "").toLowerCase(), + ) + } + ></Chips> + <Message> + Enter tag values that will trigger a language profile removal. Leave + empty if you don't want Bazarr to remove language profiles. + </Message> + </Section> <Section header="Default Settings"> <Check label="Series" diff --git a/frontend/src/pages/Settings/Languages/table.tsx b/frontend/src/pages/Settings/Languages/table.tsx index c32300628..5cfefdfa9 100644 --- a/frontend/src/pages/Settings/Languages/table.tsx +++ b/frontend/src/pages/Settings/Languages/table.tsx @@ -2,7 +2,7 @@ import { FunctionComponent, useCallback, useMemo } from "react"; import { Badge, Button, Group } from "@mantine/core"; import { faTrash, faWrench } from "@fortawesome/free-solid-svg-icons"; import { ColumnDef } from "@tanstack/react-table"; -import { cloneDeep } from "lodash"; +import { cloneDeep, includes, maxBy } from "lodash"; import { Action } from "@/components"; import { anyCutoff, @@ -66,6 +66,10 @@ const Table: FunctionComponent = () => { accessorKey: "name", }, { + header: "Tag", + accessorKey: "tag", + }, + { header: "Languages", accessorKey: "items", cell: ({ @@ -75,10 +79,10 @@ const Table: FunctionComponent = () => { }) => { return ( <Group gap="xs" wrap="nowrap"> - {items.map((v) => { + {items.map((v, i) => { const isCutoff = v.id === cutoff || cutoff === anyCutoff; return ( - <ItemBadge key={v.id} cutoff={isCutoff} item={v}></ItemBadge> + <ItemBadge key={i} cutoff={isCutoff} item={v}></ItemBadge> ); })} </Group> @@ -144,9 +148,45 @@ const Table: FunctionComponent = () => { icon={faWrench} c="gray" onClick={() => { + const lastId = maxBy(profile.items, "id")?.id || 0; + + // We once had an issue on the past where there were duplicated + // item ids that needs to become unique upon editing. + const sanitizedProfile = { + ...cloneDeep(profile), + items: profile.items.reduce( + (acc, value) => { + const { ids, duplicatedIds, items } = acc; + + // We once had an issue on the past where there were duplicated + // item ids that needs to become unique upon editing. + if (includes(ids, value.id)) { + duplicatedIds.push(value.id); + items.push({ + ...value, + id: lastId + duplicatedIds.length, + }); + + return acc; + } + + ids.push(value.id); + items.push(value); + + return acc; + }, + { + ids: [] as number[], + duplicatedIds: [] as number[], + items: [] as typeof profile.items, + }, + ).items, + tag: profile.tag || undefined, + }; + modals.openContextModal(ProfileEditModal, { languages, - profile: cloneDeep(profile), + profile: sanitizedProfile, onComplete: updateProfile, }); }} @@ -178,6 +218,7 @@ const Table: FunctionComponent = () => { const profile = { profileId: nextProfileId, name: "", + tag: undefined, items: [], cutoff: null, mustContain: [], diff --git a/frontend/src/pages/Settings/Providers/components.tsx b/frontend/src/pages/Settings/Providers/components.tsx index 72e2c3b1f..acae15261 100644 --- a/frontend/src/pages/Settings/Providers/components.tsx +++ b/frontend/src/pages/Settings/Providers/components.tsx @@ -108,10 +108,12 @@ export const ProviderView: FunctionComponent<ProviderViewProps> = ({ }) .map((v, idx) => ( <Card + titleStyles={{ overflow: "hidden", textOverflow: "ellipsis" }} key={BuildKey(v.key, idx)} header={v.name ?? capitalize(v.key)} description={v.description} onClick={() => select(v)} + lineClamp={2} ></Card> )); } else { diff --git a/frontend/src/pages/Settings/Providers/list.ts b/frontend/src/pages/Settings/Providers/list.ts index b2f9a33c7..8f0e46a56 100644 --- a/frontend/src/pages/Settings/Providers/list.ts +++ b/frontend/src/pages/Settings/Providers/list.ts @@ -218,6 +218,35 @@ export const ProviderList: Readonly<ProviderInfo[]> = [ }, ], }, + { + key: "jimaku", + name: "Jimaku.cc", + description: "Japanese Subtitles Provider", + message: + "API key required. Subtitles stem from various sources and might have quality/timing issues.", + inputs: [ + { + type: "password", + key: "api_key", + name: "API key", + }, + { + type: "switch", + key: "enable_name_search_fallback", + name: "Search by name if no AniList ID was determined (Less accurate, required for live action)", + }, + { + type: "switch", + key: "enable_archives_download", + name: "Also consider archives alongside uncompressed subtitles", + }, + { + type: "switch", + key: "enable_ai_subs", + name: "Download AI generated subtitles", + }, + ], + }, { key: "hosszupuska", description: "Hungarian Subtitles Provider" }, { key: "karagarga", @@ -276,6 +305,21 @@ export const ProviderList: Readonly<ProviderInfo[]> = [ { type: "switch", key: "skip_wrong_fps", name: "Skip Wrong FPS" }, ], }, + { + key: "legendasnet", + name: "Legendas.net", + description: "Brazilian Subtitles Provider", + inputs: [ + { + type: "text", + key: "username", + }, + { + type: "password", + key: "password", + }, + ], + }, { key: "napiprojekt", description: "Polish Subtitles Provider" }, { key: "napisy24", diff --git a/frontend/src/pages/Settings/Radarr/index.tsx b/frontend/src/pages/Settings/Radarr/index.tsx index b2e858178..264c78924 100644 --- a/frontend/src/pages/Settings/Radarr/index.tsx +++ b/frontend/src/pages/Settings/Radarr/index.tsx @@ -54,6 +54,11 @@ const SettingsRadarrView: FunctionComponent = () => { <Chips label="Excluded Tags" settingKey="settings-radarr-excluded_tags" + sanitizeFn={(values: string[] | null) => + values?.map((item) => + item.replace(/[^a-z0-9_-]/gi, "").toLowerCase(), + ) + } ></Chips> <Message> Movies with those tags (case sensitive) in Radarr will be excluded diff --git a/frontend/src/pages/Settings/Sonarr/index.tsx b/frontend/src/pages/Settings/Sonarr/index.tsx index ed66ef679..ff4ac6ca2 100644 --- a/frontend/src/pages/Settings/Sonarr/index.tsx +++ b/frontend/src/pages/Settings/Sonarr/index.tsx @@ -56,6 +56,11 @@ const SettingsSonarrView: FunctionComponent = () => { <Chips label="Excluded Tags" settingKey="settings-sonarr-excluded_tags" + sanitizeFn={(values: string[] | null) => + values?.map((item) => + item.replace(/[^a-z0-9_-]/gi, "").toLowerCase(), + ) + } ></Chips> <Message> Episodes from series with those tags (case sensitive) in Sonarr will diff --git a/frontend/src/pages/Settings/Subtitles/index.tsx b/frontend/src/pages/Settings/Subtitles/index.tsx index a2250e5a9..a2e05a5c5 100644 --- a/frontend/src/pages/Settings/Subtitles/index.tsx +++ b/frontend/src/pages/Settings/Subtitles/index.tsx @@ -1,5 +1,5 @@ -import { FunctionComponent } from "react"; -import { Code, Space, Table } from "@mantine/core"; +import React, { FunctionComponent } from "react"; +import { Code, Space, Table, Text as MantineText } from "@mantine/core"; import { Check, CollapseBox, @@ -115,14 +115,16 @@ const commandOptions: CommandOption[] = [ }, ]; -const commandOptionElements: JSX.Element[] = commandOptions.map((op, idx) => ( - <tr key={idx}> - <td> - <Code>{op.option}</Code> - </td> - <td>{op.description}</td> - </tr> -)); +const commandOptionElements: React.JSX.Element[] = commandOptions.map( + (op, idx) => ( + <tr key={idx}> + <td> + <Code>{op.option}</Code> + </td> + <td>{op.description}</td> + </tr> + ), +); const SettingsSubtitlesView: FunctionComponent = () => { return ( @@ -436,8 +438,11 @@ const SettingsSubtitlesView: FunctionComponent = () => { <Slider settingKey="settings-subsync-subsync_threshold"></Slider> <Space /> <Message> - Only series subtitles with scores <b>below</b> this value will be - automatically synchronized. + Only series subtitles with scores{" "} + <MantineText fw={700} span> + below + </MantineText>{" "} + this value will be automatically synchronized. </Message> </CollapseBox> <Check @@ -451,8 +456,11 @@ const SettingsSubtitlesView: FunctionComponent = () => { <Slider settingKey="settings-subsync-subsync_movie_threshold"></Slider> <Space /> <Message> - Only movie subtitles with scores <b>below</b> this value will be - automatically synchronized. + Only movie subtitles with scores{" "} + <MantineText fw={700} span> + below + </MantineText>{" "} + this value will be automatically synchronized. </Message> </CollapseBox> </CollapseBox> @@ -478,8 +486,11 @@ const SettingsSubtitlesView: FunctionComponent = () => { <Slider settingKey="settings-general-postprocessing_threshold"></Slider> <Space /> <Message> - Only series subtitles with scores <b>below</b> this value will be - automatically post-processed. + Only series subtitles with scores{" "} + <MantineText fw={700} span> + below + </MantineText>{" "} + this value will be automatically post-processed. </Message> </CollapseBox> <Check @@ -493,8 +504,11 @@ const SettingsSubtitlesView: FunctionComponent = () => { <Slider settingKey="settings-general-postprocessing_threshold_movie"></Slider> <Space /> <Message> - Only movie subtitles with scores <b>below</b> this value will be - automatically post-processed. + Only movie subtitles with scores{" "} + <MantineText fw={700} span> + below + </MantineText>{" "} + this value will be automatically post-processed. </Message> </CollapseBox> <Text diff --git a/frontend/src/pages/Settings/components/Card.tsx b/frontend/src/pages/Settings/components/Card.tsx index 69df15636..a8a33eec3 100644 --- a/frontend/src/pages/Settings/components/Card.tsx +++ b/frontend/src/pages/Settings/components/Card.tsx @@ -1,14 +1,23 @@ import { FunctionComponent } from "react"; -import { Center, Stack, Text, UnstyledButton } from "@mantine/core"; +import { + Center, + MantineStyleProp, + Stack, + Text, + UnstyledButton, +} from "@mantine/core"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import TextPopover from "@/components/TextPopover"; import styles from "./Card.module.scss"; interface CardProps { - header?: string; description?: string; - plus?: boolean; + header?: string; + lineClamp?: number | undefined; onClick?: () => void; + plus?: boolean; + titleStyles?: MantineStyleProp | undefined; } export const Card: FunctionComponent<CardProps> = ({ @@ -16,6 +25,8 @@ export const Card: FunctionComponent<CardProps> = ({ description, plus, onClick, + lineClamp, + titleStyles, }) => { return ( <UnstyledButton p="lg" onClick={onClick} className={styles.card}> @@ -24,9 +35,15 @@ export const Card: FunctionComponent<CardProps> = ({ <FontAwesomeIcon size="2x" icon={faPlus}></FontAwesomeIcon> </Center> ) : ( - <Stack h="100%" gap={0} align="flex-start"> - <Text fw="bold">{header}</Text> - <Text hidden={description === undefined}>{description}</Text> + <Stack h="100%" gap={0}> + <Text fw="bold" style={titleStyles}> + {header} + </Text> + <TextPopover text={description}> + <Text hidden={description === undefined} lineClamp={lineClamp}> + {description} + </Text> + </TextPopover> </Stack> )} </UnstyledButton> diff --git a/frontend/src/pages/Settings/components/forms.test.tsx b/frontend/src/pages/Settings/components/forms.test.tsx index a88d2bec7..4ec60699b 100644 --- a/frontend/src/pages/Settings/components/forms.test.tsx +++ b/frontend/src/pages/Settings/components/forms.test.tsx @@ -2,7 +2,7 @@ import { FunctionComponent, PropsWithChildren, ReactElement } from "react"; import { useForm } from "@mantine/form"; import { describe, it } from "vitest"; import { FormContext, FormValues } from "@/pages/Settings/utilities/FormValues"; -import { render, RenderOptions, screen } from "@/tests"; +import { render, screen } from "@/tests"; import { Number, Text } from "./forms"; const FormSupport: FunctionComponent<PropsWithChildren> = ({ children }) => { @@ -15,10 +15,8 @@ const FormSupport: FunctionComponent<PropsWithChildren> = ({ children }) => { return <FormContext.Provider value={form}>{children}</FormContext.Provider>; }; -const formRender = ( - ui: ReactElement, - options?: Omit<RenderOptions, "wrapper">, -) => render(<FormSupport>{ui}</FormSupport>); +const formRender = (ui: ReactElement) => + render(<FormSupport>{ui}</FormSupport>); describe("Settings form", () => { describe("number component", () => { diff --git a/frontend/src/pages/Settings/components/forms.tsx b/frontend/src/pages/Settings/components/forms.tsx index 95134db92..43b559736 100644 --- a/frontend/src/pages/Settings/components/forms.tsx +++ b/frontend/src/pages/Settings/components/forms.tsx @@ -1,4 +1,4 @@ -import { FunctionComponent, ReactNode, ReactText } from "react"; +import { FunctionComponent, ReactNode } from "react"; import { Input, NumberInput, @@ -49,7 +49,7 @@ export const Number: FunctionComponent<NumberProps> = (props) => { ); }; -export type TextProps = BaseInput<ReactText> & TextInputProps; +export type TextProps = BaseInput<string | number> & TextInputProps; export const Text: FunctionComponent<TextProps> = (props) => { const { value, update, rest } = useBaseInput(props); @@ -86,11 +86,7 @@ export interface CheckProps extends BaseInput<boolean> { inline?: boolean; } -export const Check: FunctionComponent<CheckProps> = ({ - label, - inline, - ...props -}) => { +export const Check: FunctionComponent<CheckProps> = ({ label, ...props }) => { const { value, update, rest } = useBaseInput(props); return ( @@ -160,13 +156,25 @@ export const Slider: FunctionComponent<SliderProps> = (props) => { }; type ChipsProp = BaseInput<string[]> & - Omit<ChipInputProps, "onChange" | "data">; + Omit<ChipInputProps, "onChange" | "data"> & { + sanitizeFn?: (values: string[] | null) => string[] | undefined; + }; export const Chips: FunctionComponent<ChipsProp> = (props) => { const { value, update, rest } = useBaseInput(props); + const handleChange = (value: string[] | null) => { + const sanitizedValues = props.sanitizeFn?.(value) ?? value; + + update(sanitizedValues || null); + }; + return ( - <ChipInput {...rest} value={value ?? []} onChange={update}></ChipInput> + <ChipInput + {...rest} + value={value ?? []} + onChange={handleChange} + ></ChipInput> ); }; diff --git a/frontend/src/pages/System/Announcements/table.tsx b/frontend/src/pages/System/Announcements/table.tsx index febb32fa1..910fb4bd5 100644 --- a/frontend/src/pages/System/Announcements/table.tsx +++ b/frontend/src/pages/System/Announcements/table.tsx @@ -19,7 +19,7 @@ const Table: FunctionComponent<Props> = ({ announcements }) => { () => [ { header: "Since", - accessor: "timestamp", + accessorKey: "timestamp", cell: ({ row: { original: { timestamp }, @@ -30,7 +30,7 @@ const Table: FunctionComponent<Props> = ({ announcements }) => { }, { header: "Announcement", - accessor: "text", + accessorKey: "text", cell: ({ row: { original: { text }, @@ -41,7 +41,7 @@ const Table: FunctionComponent<Props> = ({ announcements }) => { }, { header: "More Info", - accessor: "link", + accessorKey: "link", cell: ({ row: { original: { link }, @@ -56,7 +56,7 @@ const Table: FunctionComponent<Props> = ({ announcements }) => { }, { header: "Dismiss", - accessor: "hash", + accessorKey: "hash", cell: ({ row: { original: { dismissible, hash }, diff --git a/frontend/src/pages/System/Status/index.tsx b/frontend/src/pages/System/Status/index.tsx index bcd0e175d..157935dfb 100644 --- a/frontend/src/pages/System/Status/index.tsx +++ b/frontend/src/pages/System/Status/index.tsx @@ -144,6 +144,8 @@ const SystemStatusView: FunctionComponent = () => { <Row title="Radarr Version">{status?.radarr_version}</Row> <Row title="Operating System">{status?.operating_system}</Row> <Row title="Python Version">{status?.python_version}</Row> + <Row title="Database Engine">{status?.database_engine}</Row> + <Row title="Database Version">{status?.database_migration}</Row> <Row title="Bazarr Directory">{status?.bazarr_directory}</Row> <Row title="Bazarr Config Directory"> {status?.bazarr_config_directory} diff --git a/frontend/src/pages/System/Tasks/table.tsx b/frontend/src/pages/System/Tasks/table.tsx index ed3248b6f..5e1b045bd 100644 --- a/frontend/src/pages/System/Tasks/table.tsx +++ b/frontend/src/pages/System/Tasks/table.tsx @@ -17,7 +17,7 @@ const Table: FunctionComponent<Props> = ({ tasks }) => { () => [ { header: "Name", - accessor: "name", + accessorKey: "name", cell: ({ row: { original: { name }, @@ -28,7 +28,7 @@ const Table: FunctionComponent<Props> = ({ tasks }) => { }, { header: "Interval", - accessor: "interval", + accessorKey: "interval", cell: ({ row: { original: { interval }, @@ -39,11 +39,11 @@ const Table: FunctionComponent<Props> = ({ tasks }) => { }, { header: "Next Execution", - accessor: "next_run_in", + accessorKey: "next_run_in", }, { header: "Run", - accessor: "job_running", + accessorKey: "job_running", cell: ({ row: { original: { job_id: jobId, job_running: jobRunning }, diff --git a/frontend/src/pages/Wanted/Movies/index.tsx b/frontend/src/pages/Wanted/Movies/index.tsx index 7c497f799..c05cfb7c3 100644 --- a/frontend/src/pages/Wanted/Movies/index.tsx +++ b/frontend/src/pages/Wanted/Movies/index.tsx @@ -21,7 +21,7 @@ const WantedMoviesView: FunctionComponent = () => { () => [ { header: "Name", - accessor: "title", + accessorKey: "title", cell: ({ row: { original: { title, radarrId }, @@ -37,7 +37,7 @@ const WantedMoviesView: FunctionComponent = () => { }, { header: "Missing", - accessor: "missing_subtitles", + accessorKey: "missing_subtitles", cell: ({ row: { original: { radarrId, missing_subtitles: missingSubtitles }, diff --git a/frontend/src/pages/views/ItemOverview.tsx b/frontend/src/pages/views/ItemOverview.tsx index 15b43aab1..36d296850 100644 --- a/frontend/src/pages/views/ItemOverview.tsx +++ b/frontend/src/pages/views/ItemOverview.tsx @@ -31,6 +31,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Language } from "@/components/bazarr"; import { BuildKey } from "@/utilities"; import { + normalizeAudioLanguage, useLanguageProfileBy, useProfileItemsToLanguages, } from "@/utilities/languages"; @@ -87,7 +88,7 @@ const ItemOverview: FunctionComponent<Props> = (props) => { icon={faMusic} title="Audio Language" > - {v.name} + {normalizeAudioLanguage(v.name)} </ItemBadge> )) ?? [], [item?.audio_language], @@ -142,12 +143,7 @@ const ItemOverview: FunctionComponent<Props> = (props) => { }} > <Grid.Col span={3} visibleFrom="sm"> - <Image - src={item?.poster} - mx="auto" - maw="250px" - fallbackSrc="https://placehold.co/250x250?text=Placeholder" - ></Image> + <Image src={item?.poster} mx="auto" maw="250px"></Image> </Grid.Col> <Grid.Col span={8} maw="100%" style={{ overflow: "hidden" }}> <Stack align="flex-start" gap="xs" mx={6}> |