From 57d3f69efad74ebcf6d7adeed7a57811cb67a9fa Mon Sep 17 00:00:00 2001 From: Isra Date: Sat, 16 Mar 2024 14:55:29 -0500 Subject: [PATCH 01/20] Drop subtitles --- src/assets/locales/en.json | 2 +- src/components/DropFile.tsx | 51 +++++++++++++++++++ .../player/atoms/settings/CaptionsView.tsx | 32 +++++++++++- 3 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 src/components/DropFile.tsx diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index dc4d6ed1..59bc992d 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -316,7 +316,7 @@ "unknownOption": "Unknown" }, "subtitles": { - "customChoice": "Select subtitle from file", + "customChoice": "Drop or upload file", "customizeLabel": "Customize", "offChoice": "Off", "settings": { diff --git a/src/components/DropFile.tsx b/src/components/DropFile.tsx new file mode 100644 index 00000000..b432c2ce --- /dev/null +++ b/src/components/DropFile.tsx @@ -0,0 +1,51 @@ +import { useEffect, useState } from "react"; +import type { DragEvent, ReactNode } from "react"; + +interface FileDropHandlerProps { + children: ReactNode; + className: string; + onDrop: (event: DragEvent) => void; + onDraggingChange: (isDragging: boolean) => void; +} + +export function FileDropHandler(props: FileDropHandlerProps) { + const [dragging, setDragging] = useState(false); + + const handleDragEnter = (event: DragEvent) => { + event.preventDefault(); + setDragging(true); + }; + + const handleDragLeave = (event: DragEvent) => { + if (!event.currentTarget.contains(event.relatedTarget as Node)) { + setDragging(false); + } + }; + + const handleDragOver = (event: DragEvent) => { + event.preventDefault(); + }; + + const handleDrop = (event: DragEvent) => { + event.preventDefault(); + setDragging(false); + + props.onDrop(event); + }; + + useEffect(() => { + props.onDraggingChange(dragging); + }, [dragging, props]); + + return ( +
+ {props.children} +
+ ); +} diff --git a/src/components/player/atoms/settings/CaptionsView.tsx b/src/components/player/atoms/settings/CaptionsView.tsx index 8524ecc8..627787ba 100644 --- a/src/components/player/atoms/settings/CaptionsView.tsx +++ b/src/components/player/atoms/settings/CaptionsView.tsx @@ -5,6 +5,7 @@ import { useAsyncFn } from "react-use"; import { convert } from "subsrt-ts"; import { subtitleTypeList } from "@/backend/helpers/subs"; +import { FileDropHandler } from "@/components/DropFile"; import { FlagIcon } from "@/components/FlagIcon"; import { useCaptions } from "@/components/player/hooks/useCaptions"; import { Menu } from "@/components/player/internals/ContextMenu"; @@ -123,6 +124,8 @@ export function CaptionsView({ id }: { id: string }) { const { selectCaptionById, disable } = useCaptions(); const captionList = usePlayerStore((s) => s.captionList); const getHlsCaptionList = usePlayerStore((s) => s.display?.getCaptionList); + const [dragging, setDragging] = useState(false); + const setCaption = usePlayerStore((s) => s.setCaption); const captions = useMemo( () => @@ -162,7 +165,31 @@ export function CaptionsView({ id }: { id: string }) { }); return ( - <> + { + setDragging(isDragging); + }} + onDrop={(event) => { + const files = event.dataTransfer.files; + if (!files || files.length === 0) return; + + const reader = new FileReader(); + reader.addEventListener("load", (e) => { + if (!e.target || typeof e.target.result !== "string") return; + + const converted = convert(e.target.result, "srt"); + + setCaption({ + language: "custom", + srtData: converted, + id: "custom-caption", + }); + }); + + reader.readAsText(files[0]); + }} + >
router.navigate("/")} @@ -182,6 +209,7 @@ export function CaptionsView({ id }: { id: string }) {
+ disable()} selected={!selectedCaptionId}> {t("player.menus.subtitles.offChoice")} @@ -189,7 +217,7 @@ export function CaptionsView({ id }: { id: string }) { {content} - +
); } From c08dea89d1231d85712612469b1181eca45ab6d1 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Mon, 18 Mar 2024 00:06:27 +0100 Subject: [PATCH 02/20] add audio track selector --- src/components/player/atoms/Settings.tsx | 6 +++ .../player/atoms/settings/AudioView.tsx | 42 +++++++++++++++++++ .../player/atoms/settings/SettingsMenu.tsx | 8 ++++ src/components/player/display/base.ts | 32 ++++++++++++++ src/components/player/display/chromecast.ts | 3 ++ .../player/display/displayInterface.ts | 5 ++- src/stores/player/slices/display.ts | 10 +++++ src/stores/player/slices/source.ts | 10 +++++ 8 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/components/player/atoms/settings/AudioView.tsx diff --git a/src/components/player/atoms/Settings.tsx b/src/components/player/atoms/Settings.tsx index 68e36b83..5900df44 100644 --- a/src/components/player/atoms/Settings.tsx +++ b/src/components/player/atoms/Settings.tsx @@ -14,6 +14,7 @@ import { Menu } from "@/components/player/internals/ContextMenu"; import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { usePlayerStore } from "@/stores/player/store"; +import { AudioView } from "./settings/AudioView"; import { CaptionSettingsView } from "./settings/CaptionSettingsView"; import { CaptionsView } from "./settings/CaptionsView"; import { DownloadRoutes } from "./settings/Downloads"; @@ -46,6 +47,11 @@ function SettingsOverlay({ id }: { id: string }) { + + + + + diff --git a/src/components/player/atoms/settings/AudioView.tsx b/src/components/player/atoms/settings/AudioView.tsx new file mode 100644 index 00000000..0158ea78 --- /dev/null +++ b/src/components/player/atoms/settings/AudioView.tsx @@ -0,0 +1,42 @@ +import { t } from "i18next"; +import { useCallback } from "react"; + +import { Menu } from "@/components/player/internals/ContextMenu"; +import { useOverlayRouter } from "@/hooks/useOverlayRouter"; +import { AudioTrack } from "@/stores/player/slices/source"; +import { usePlayerStore } from "@/stores/player/store"; + +import { SelectableLink } from "../../internals/ContextMenu/Links"; + +export function AudioView({ id }: { id: string }) { + const router = useOverlayRouter(id); + const audioTracks = usePlayerStore((s) => s.audioTracks); + const currentAudioTrack = usePlayerStore((s) => s.currentAudioTrack); + const changeAudioTrack = usePlayerStore((s) => s.display?.changeAudioTrack); + + const change = useCallback( + (track: AudioTrack) => { + changeAudioTrack?.(track); + router.close(); + }, + [router, changeAudioTrack], + ); + + return ( + <> + router.navigate("/")}>Audio + + {audioTracks.map((v) => ( + change(v) : undefined} + disabled={!audioTracks.includes(v)} + > + {v.label} ({v.language}) + + ))} + + + ); +} diff --git a/src/components/player/atoms/settings/SettingsMenu.tsx b/src/components/player/atoms/settings/SettingsMenu.tsx index 8321c562..8225277b 100644 --- a/src/components/player/atoms/settings/SettingsMenu.tsx +++ b/src/components/player/atoms/settings/SettingsMenu.tsx @@ -16,6 +16,7 @@ export function SettingsMenu({ id }: { id: string }) { const { t } = useTranslation(); const router = useOverlayRouter(id); const currentQuality = usePlayerStore((s) => s.currentQuality); + const currentAudioTrack = usePlayerStore((s) => s.currentAudioTrack); const selectedCaptionLanguage = usePlayerStore( (s) => s.caption.selected?.language, ); @@ -51,6 +52,13 @@ export function SettingsMenu({ id }: { id: string }) { > {t("player.menus.settings.qualityItem")} + router.navigate("/audio")} + rightText={currentAudioTrack ? currentAudioTrack.label : ""} + > + {/* {t("player.menus.settings.qualityItem")} */} + Audio + router.navigate("/source")} rightText={sourceName} diff --git a/src/components/player/display/base.ts b/src/components/player/display/base.ts index 51e6d7bb..8e155f69 100644 --- a/src/components/player/display/base.ts +++ b/src/components/player/display/base.ts @@ -81,6 +81,24 @@ export function makeVideoElementDisplayInterface(): DisplayInterface { emit("qualities", convertedLevels); } + function reportAudioTracks() { + if (!hls) return; + const currentTrack = hls.audioTracks[hls.audioTrack]; + emit("changedaudiotrack", { + id: currentTrack.id.toString(), + label: currentTrack.name, + language: currentTrack.lang ?? "unknown", + }); + emit( + "audiotracks", + hls.audioTracks.map((v) => ({ + id: v.id.toString(), + label: v.name, + language: v.lang ?? "unknown", + })), + ); + } + function setupQualityForHls() { if (videoElement && canPlayHlsNatively(videoElement)) { return; // nothing to change @@ -155,6 +173,7 @@ export function makeVideoElementDisplayInterface(): DisplayInterface { if (!hls) return; reportLevels(); setupQualityForHls(); + reportAudioTracks(); if (isExtensionActiveCached()) { hls.on(Hls.Events.LEVEL_LOADED, async (_, data) => { @@ -464,5 +483,18 @@ export function makeVideoElementDisplayInterface(): DisplayInterface { hls?.setSubtitleOption({ lang }); return promise; }, + changeAudioTrack(track) { + if (!hls) return; + const audioTrack = hls?.audioTracks.find( + (t) => t.id.toString() === track.id, + ); + if (!audioTrack) return; + hls.audioTrack = hls.audioTracks.indexOf(audioTrack); + emit("changedaudiotrack", { + id: audioTrack.id.toString(), + label: audioTrack.name, + language: audioTrack.lang ?? "unknown", + }); + }, }; } diff --git a/src/components/player/display/chromecast.ts b/src/components/player/display/chromecast.ts index 1a318f16..48f8b2ab 100644 --- a/src/components/player/display/chromecast.ts +++ b/src/components/player/display/chromecast.ts @@ -283,5 +283,8 @@ export function makeChromecastDisplayInterface( async setSubtitlePreference() { return Promise.resolve(); }, + changeAudioTrack() { + // cant change audio tracks + }, }; } diff --git a/src/components/player/display/displayInterface.ts b/src/components/player/display/displayInterface.ts index 134bef44..2f17aaed 100644 --- a/src/components/player/display/displayInterface.ts +++ b/src/components/player/display/displayInterface.ts @@ -1,7 +1,7 @@ import { MediaPlaylist } from "hls.js"; import { MWMediaType } from "@/backend/metadata/types/mw"; -import { CaptionListItem } from "@/stores/player/slices/source"; +import { AudioTrack, CaptionListItem } from "@/stores/player/slices/source"; import { LoadableSource, SourceQuality } from "@/stores/player/utils/qualities"; import { Listener } from "@/utils/events"; @@ -25,6 +25,8 @@ export type DisplayInterfaceEvents = { loading: boolean; qualities: SourceQuality[]; changedquality: SourceQuality | null; + audiotracks: AudioTrack[]; + changedaudiotrack: AudioTrack | null; needstrack: boolean; canairplay: boolean; playbackrate: number; @@ -60,6 +62,7 @@ export interface DisplayInterface extends Listener { automaticQuality: boolean, preferredQuality: SourceQuality | null, ): void; + changeAudioTrack(audioTrack: AudioTrack): void; processVideoElement(video: HTMLVideoElement): void; processContainerElement(container: HTMLElement): void; toggleFullscreen(): void; diff --git a/src/stores/player/slices/display.ts b/src/stores/player/slices/display.ts index 86743ccd..63403376 100644 --- a/src/stores/player/slices/display.ts +++ b/src/stores/player/slices/display.ts @@ -75,6 +75,16 @@ export const createDisplaySlice: MakeSlice = (set, get) => ({ s.currentQuality = quality; }); }); + newDisplay.on("audiotracks", (audioTracks) => { + set((s) => { + s.audioTracks = audioTracks; + }); + }); + newDisplay.on("changedaudiotrack", (audioTrack) => { + set((s) => { + s.currentAudioTrack = audioTrack; + }); + }); newDisplay.on("needstrack", (needsTrack) => { set((s) => { s.caption.asTrack = needsTrack; diff --git a/src/stores/player/slices/source.ts b/src/stores/player/slices/source.ts index 5d04ef49..eb2ce9e1 100644 --- a/src/stores/player/slices/source.ts +++ b/src/stores/player/slices/source.ts @@ -56,12 +56,20 @@ export interface CaptionListItem { hls?: boolean; } +export interface AudioTrack { + id: string; + label: string; + language: string; +} + export interface SourceSlice { status: PlayerStatus; source: SourceSliceSource | null; sourceId: string | null; qualities: SourceQuality[]; + audioTracks: AudioTrack[]; currentQuality: SourceQuality | null; + currentAudioTrack: AudioTrack | null; captionList: CaptionListItem[]; caption: { selected: Caption | null; @@ -109,8 +117,10 @@ export const createSourceSlice: MakeSlice = (set, get) => ({ source: null, sourceId: null, qualities: [], + audioTracks: [], captionList: [], currentQuality: null, + currentAudioTrack: null, status: playerStatus.IDLE, meta: null, caption: { From c9d2d7134bbd3a01e6323b8993ae3d3802aea352 Mon Sep 17 00:00:00 2001 From: Isra Date: Mon, 18 Mar 2024 13:41:10 -0500 Subject: [PATCH 03/20] Fix issues --- src/assets/locales/en.json | 3 +- src/components/DropFile.tsx | 4 +- .../player/atoms/settings/CaptionsView.tsx | 96 ++++++++++++------- 3 files changed, 64 insertions(+), 39 deletions(-) diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index 59bc992d..bcb7e293 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -325,7 +325,8 @@ "fixCapitals": "Fix capitalization" }, "title": "Subtitles", - "unknownLanguage": "Unknown" + "unknownLanguage": "Unknown", + "dropSubtitleFile": "Drop subtitle file here" } }, "metadata": { diff --git a/src/components/DropFile.tsx b/src/components/DropFile.tsx index b432c2ce..8b0ab84e 100644 --- a/src/components/DropFile.tsx +++ b/src/components/DropFile.tsx @@ -38,7 +38,7 @@ export function FileDropHandler(props: FileDropHandlerProps) { }, [dragging, props]); return ( -
{props.children} -
+ ); } diff --git a/src/components/player/atoms/settings/CaptionsView.tsx b/src/components/player/atoms/settings/CaptionsView.tsx index 627787ba..a3196929 100644 --- a/src/components/player/atoms/settings/CaptionsView.tsx +++ b/src/components/player/atoms/settings/CaptionsView.tsx @@ -1,5 +1,5 @@ import Fuse from "fuse.js"; -import { useMemo, useRef, useState } from "react"; +import { type DragEvent, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useAsyncFn } from "react-use"; import { convert } from "subsrt-ts"; @@ -7,6 +7,7 @@ import { convert } from "subsrt-ts"; import { subtitleTypeList } from "@/backend/helpers/subs"; import { FileDropHandler } from "@/components/DropFile"; import { FlagIcon } from "@/components/FlagIcon"; +import { Icon, Icons } from "@/components/Icon"; import { useCaptions } from "@/components/player/hooks/useCaptions"; import { Menu } from "@/components/player/internals/ContextMenu"; import { Input } from "@/components/player/internals/ContextMenu/Input"; @@ -127,6 +128,32 @@ export function CaptionsView({ id }: { id: string }) { const [dragging, setDragging] = useState(false); const setCaption = usePlayerStore((s) => s.setCaption); + function onDrop(event: DragEvent) { + const files = event.dataTransfer.files; + const firstFile = files[0]; + if (!files || !firstFile) return; + + const fileExtension = `.${firstFile.name.split(".").pop()}`; + if (!fileExtension || !subtitleTypeList.includes(fileExtension)) { + return; + } + + const reader = new FileReader(); + reader.addEventListener("load", (e) => { + if (!e.target || typeof e.target.result !== "string") return; + + const converted = convert(e.target.result, "srt"); + + setCaption({ + language: "custom", + srtData: converted, + id: "custom-caption", + }); + }); + + reader.readAsText(firstFile); + } + const captions = useMemo( () => captionList.length !== 0 ? captionList : getHlsCaptionList?.() ?? [], @@ -165,32 +192,19 @@ export function CaptionsView({ id }: { id: string }) { }); return ( - { - setDragging(isDragging); - }} - onDrop={(event) => { - const files = event.dataTransfer.files; - if (!files || files.length === 0) return; - - const reader = new FileReader(); - reader.addEventListener("load", (e) => { - if (!e.target || typeof e.target.result !== "string") return; - - const converted = convert(e.target.result, "srt"); - - setCaption({ - language: "custom", - srtData: converted, - id: "custom-caption", - }); - }); - - reader.readAsText(files[0]); - }} - > + <>
+ {dragging && ( +
+
+ + + {t("player.menus.subtitles.dropSubtitleFile")} + +
+
+ )} + router.navigate("/")} rightSide={ @@ -205,19 +219,29 @@ export function CaptionsView({ id }: { id: string }) { > {t("player.menus.subtitles.title")} +
+ { + setDragging(isDragging); + }} + onDrop={(event) => onDrop(event)} + >
- - - - disable()} selected={!selectedCaptionId}> - {t("player.menus.subtitles.offChoice")} - - - {content} - -
+ + disable()} + selected={!selectedCaptionId} + > + {t("player.menus.subtitles.offChoice")} + + + {content} + +
+ ); } From a26097ffd4cdbeced29b7830a63464f2a792b725 Mon Sep 17 00:00:00 2001 From: Isra Date: Mon, 18 Mar 2024 13:47:05 -0500 Subject: [PATCH 04/20] Bold and change size --- src/components/player/atoms/settings/CaptionsView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/player/atoms/settings/CaptionsView.tsx b/src/components/player/atoms/settings/CaptionsView.tsx index a3196929..62fc4ac7 100644 --- a/src/components/player/atoms/settings/CaptionsView.tsx +++ b/src/components/player/atoms/settings/CaptionsView.tsx @@ -197,8 +197,8 @@ export function CaptionsView({ id }: { id: string }) { {dragging && (
- - + + {t("player.menus.subtitles.dropSubtitleFile")}
From a172322a54071a25bf318b8645edf4f030fc253a Mon Sep 17 00:00:00 2001 From: Isra Date: Mon, 18 Mar 2024 15:50:56 -0500 Subject: [PATCH 05/20] Animate show/hide --- .../player/atoms/settings/CaptionsView.tsx | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/player/atoms/settings/CaptionsView.tsx b/src/components/player/atoms/settings/CaptionsView.tsx index 62fc4ac7..37a5fa6e 100644 --- a/src/components/player/atoms/settings/CaptionsView.tsx +++ b/src/components/player/atoms/settings/CaptionsView.tsx @@ -1,3 +1,4 @@ +import classNames from "classnames"; import Fuse from "fuse.js"; import { type DragEvent, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -194,16 +195,19 @@ export function CaptionsView({ id }: { id: string }) { return ( <>
- {dragging && ( -
-
- - - {t("player.menus.subtitles.dropSubtitleFile")} - -
+
+
+ + + {t("player.menus.subtitles.dropSubtitleFile")} +
- )} +
router.navigate("/")} From b2ec99c4d19add5a6216657abdf4a6db4d338482 Mon Sep 17 00:00:00 2001 From: Isra Date: Mon, 18 Mar 2024 22:08:58 -0500 Subject: [PATCH 06/20] Make bg darker --- src/components/player/atoms/settings/CaptionsView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/player/atoms/settings/CaptionsView.tsx b/src/components/player/atoms/settings/CaptionsView.tsx index 37a5fa6e..88614c71 100644 --- a/src/components/player/atoms/settings/CaptionsView.tsx +++ b/src/components/player/atoms/settings/CaptionsView.tsx @@ -225,7 +225,7 @@ export function CaptionsView({ id }: { id: string }) {
{ setDragging(isDragging); }} From 6ba53ec29a9e0f332173f9827b687505e9b1709d Mon Sep 17 00:00:00 2001 From: Jorrin Date: Sun, 24 Mar 2024 01:04:11 +0100 Subject: [PATCH 07/20] only show audio when its available --- src/assets/locales/en.json | 1 + .../player/atoms/settings/SettingsMenu.tsx | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index dc4d6ed1..60c08bfe 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -294,6 +294,7 @@ "enableSubtitles": "Enable Subtitles", "experienceSection": "Viewing experience", "playbackItem": "Playback settings", + "audioItem": "Audio", "qualityItem": "Quality", "sourceItem": "Video sources", "subtitleItem": "Subtitle settings", diff --git a/src/components/player/atoms/settings/SettingsMenu.tsx b/src/components/player/atoms/settings/SettingsMenu.tsx index 8225277b..e47dc6f5 100644 --- a/src/components/player/atoms/settings/SettingsMenu.tsx +++ b/src/components/player/atoms/settings/SettingsMenu.tsx @@ -52,13 +52,15 @@ export function SettingsMenu({ id }: { id: string }) { > {t("player.menus.settings.qualityItem")} - router.navigate("/audio")} - rightText={currentAudioTrack ? currentAudioTrack.label : ""} - > - {/* {t("player.menus.settings.qualityItem")} */} - Audio - + {currentAudioTrack && ( + router.navigate("/audio")} + rightText={currentAudioTrack ? currentAudioTrack.label : ""} + > + {t("player.menus.settings.audioItem")} + + )} + router.navigate("/source")} rightText={sourceName} From c2e587bf90b58358dc6ff04e5c07c2e3b4a71607 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Sun, 24 Mar 2024 01:52:16 +0100 Subject: [PATCH 08/20] unused import --- src/components/player/atoms/settings/AudioView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/player/atoms/settings/AudioView.tsx b/src/components/player/atoms/settings/AudioView.tsx index 0158ea78..0d18ada8 100644 --- a/src/components/player/atoms/settings/AudioView.tsx +++ b/src/components/player/atoms/settings/AudioView.tsx @@ -1,4 +1,3 @@ -import { t } from "i18next"; import { useCallback } from "react"; import { Menu } from "@/components/player/internals/ContextMenu"; From dbb1c1979664d1ae053f6f5e0bac68cd1344b378 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Sun, 24 Mar 2024 23:54:09 +0100 Subject: [PATCH 09/20] add flags to audio options --- .../player/atoms/settings/AudioView.tsx | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/components/player/atoms/settings/AudioView.tsx b/src/components/player/atoms/settings/AudioView.tsx index 0d18ada8..a2c9c7f6 100644 --- a/src/components/player/atoms/settings/AudioView.tsx +++ b/src/components/player/atoms/settings/AudioView.tsx @@ -1,13 +1,37 @@ import { useCallback } from "react"; +import { useTranslation } from "react-i18next"; +import { FlagIcon } from "@/components/FlagIcon"; import { Menu } from "@/components/player/internals/ContextMenu"; import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { AudioTrack } from "@/stores/player/slices/source"; import { usePlayerStore } from "@/stores/player/store"; +import { getPrettyLanguageNameFromLocale } from "@/utils/language"; import { SelectableLink } from "../../internals/ContextMenu/Links"; +export function AudioOption(props: { + langCode?: string; + children: React.ReactNode; + selected?: boolean; + onClick?: () => void; +}) { + return ( + + + + + + {props.children} + + + ); +} + export function AudioView({ id }: { id: string }) { + const { t } = useTranslation(); + const unknownChoice = t("player.menus.subtitles.unknownLanguage"); + const router = useOverlayRouter(id); const audioTracks = usePlayerStore((s) => s.audioTracks); const currentAudioTrack = usePlayerStore((s) => s.currentAudioTrack); @@ -26,14 +50,14 @@ export function AudioView({ id }: { id: string }) { router.navigate("/")}>Audio {audioTracks.map((v) => ( - change(v) : undefined} - disabled={!audioTracks.includes(v)} > - {v.label} ({v.language}) - + {getPrettyLanguageNameFromLocale(v.language) ?? unknownChoice} + ))} From e19ac55847d5d560e15f1e25a21482e17a324c1f Mon Sep 17 00:00:00 2001 From: Jorrin Date: Sun, 24 Mar 2024 23:55:58 +0100 Subject: [PATCH 10/20] add pretty language to menu selection --- src/components/player/atoms/settings/SettingsMenu.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/player/atoms/settings/SettingsMenu.tsx b/src/components/player/atoms/settings/SettingsMenu.tsx index e47dc6f5..8198d87b 100644 --- a/src/components/player/atoms/settings/SettingsMenu.tsx +++ b/src/components/player/atoms/settings/SettingsMenu.tsx @@ -36,6 +36,11 @@ export function SettingsMenu({ id }: { id: string }) { t("player.menus.subtitles.unknownLanguage") : undefined; + const selectedAudioLanguagePretty = currentAudioTrack + ? getPrettyLanguageNameFromLocale(currentAudioTrack.language) ?? + t("player.menus.subtitles.unknownLanguage") + : undefined; + const source = usePlayerStore((s) => s.source); const downloadable = source?.type === "file" || source?.type === "hls"; @@ -55,7 +60,7 @@ export function SettingsMenu({ id }: { id: string }) { {currentAudioTrack && ( router.navigate("/audio")} - rightText={currentAudioTrack ? currentAudioTrack.label : ""} + rightText={selectedAudioLanguagePretty ?? undefined} > {t("player.menus.settings.audioItem")} From e54077045df9cf6e0feb8c117ec6bf2b7082a1ba Mon Sep 17 00:00:00 2001 From: Isra Date: Tue, 26 Mar 2024 00:29:47 -0500 Subject: [PATCH 11/20] Change icon --- src/components/Icon.tsx | 2 ++ src/components/player/atoms/settings/CaptionsView.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index ec5e26cb..500b408a 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -64,6 +64,7 @@ export enum Icons { DONATION = "donation", CIRCLE_QUESTION = "circle_question", BRUSH = "brush", + UPLOAD = "upload", } export interface IconProps { @@ -134,6 +135,7 @@ const iconList: Record = { donation: ``, circle_question: ``, brush: ``, + upload: ``, }; function ChromeCastButton() { diff --git a/src/components/player/atoms/settings/CaptionsView.tsx b/src/components/player/atoms/settings/CaptionsView.tsx index 88614c71..035567e2 100644 --- a/src/components/player/atoms/settings/CaptionsView.tsx +++ b/src/components/player/atoms/settings/CaptionsView.tsx @@ -202,7 +202,7 @@ export function CaptionsView({ id }: { id: string }) { )} >
- + {t("player.menus.subtitles.dropSubtitleFile")} From 42d107dd9d50d6dd6f887b5782bc1e843002b1a3 Mon Sep 17 00:00:00 2001 From: qtchaos <72168435+qtchaos@users.noreply.github.com> Date: Tue, 26 Mar 2024 18:45:24 +0200 Subject: [PATCH 12/20] fix: stop failed test from leaking worker url --- src/pages/parts/admin/WorkerTestPart.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/parts/admin/WorkerTestPart.tsx b/src/pages/parts/admin/WorkerTestPart.tsx index 5f8b9853..bb68fced 100644 --- a/src/pages/parts/admin/WorkerTestPart.tsx +++ b/src/pages/parts/admin/WorkerTestPart.tsx @@ -83,10 +83,12 @@ export function WorkerTestPart() { status: "success", }); } catch (err) { + const error = err as Error; + error.message = error.message.replace(worker.url, "WORKER_URL"); updateWorker(worker.id, { id: worker.id, status: "error", - error: err as Error, + error, }); } }); From c4f68615cd2fc0efe959506e4747439ff6165f87 Mon Sep 17 00:00:00 2001 From: Honkertonken <94032937+Honkertonken@users.noreply.github.com> Date: Wed, 27 Mar 2024 18:20:57 +0530 Subject: [PATCH 13/20] Update tmdb.ts --- src/backend/metadata/tmdb.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/backend/metadata/tmdb.ts b/src/backend/metadata/tmdb.ts index b143b312..88412c84 100644 --- a/src/backend/metadata/tmdb.ts +++ b/src/backend/metadata/tmdb.ts @@ -173,12 +173,17 @@ export async function multiSearch( language: "en-US", page: 1, }); - // filter out results that aren't movies or shows - const results = data.results.filter( - (r) => - r.media_type === TMDBContentTypes.MOVIE || - r.media_type === TMDBContentTypes.TV, - ); + const currentDate = new Date(); + // filter out results that aren't movies or shows or are unreleased + const results = data.results.filter((r) => { + if (r.media_type === TMDBContentTypes.MOVIE) { + return new Date(r.release_date) <= currentDate; + } + if (r.media_type === TMDBContentTypes.TV) { + return new Date(r.first_air_date) <= currentDate; + } + return false; + }); return results; } From e431626bfa767b5c8c1e2b328a39171b1c9d912b Mon Sep 17 00:00:00 2001 From: Honkertonken <94032937+Honkertonken@users.noreply.github.com> Date: Wed, 27 Mar 2024 23:19:12 +0530 Subject: [PATCH 14/20] Update MediaCard.tsx --- src/components/media/MediaCard.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/media/MediaCard.tsx b/src/components/media/MediaCard.tsx index cad3ae6a..ac6c243c 100644 --- a/src/components/media/MediaCard.tsx +++ b/src/components/media/MediaCard.tsx @@ -35,10 +35,14 @@ function MediaCardContent({ const { t } = useTranslation(); const percentageString = `${Math.round(percentage ?? 0).toFixed(0)}%`; - const canLink = linkable && !closable; + const canLink = linkable && !closable && !!media.year; const dotListContent = [t(`media.types.${media.type}`)]; - if (media.year) dotListContent.push(media.year.toFixed()); + if (media.year) { + dotListContent.push(media.year.toFixed()); + } else { + dotListContent.push(t("Unreleased")); + } return (
{content}; + if (!props.linkable || !props.media.year) return {content}; return ( Date: Wed, 27 Mar 2024 23:19:35 +0530 Subject: [PATCH 15/20] Revert "Update tmdb.ts" This reverts commit c4f68615cd2fc0efe959506e4747439ff6165f87. --- src/backend/metadata/tmdb.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/backend/metadata/tmdb.ts b/src/backend/metadata/tmdb.ts index 88412c84..b143b312 100644 --- a/src/backend/metadata/tmdb.ts +++ b/src/backend/metadata/tmdb.ts @@ -173,17 +173,12 @@ export async function multiSearch( language: "en-US", page: 1, }); - const currentDate = new Date(); - // filter out results that aren't movies or shows or are unreleased - const results = data.results.filter((r) => { - if (r.media_type === TMDBContentTypes.MOVIE) { - return new Date(r.release_date) <= currentDate; - } - if (r.media_type === TMDBContentTypes.TV) { - return new Date(r.first_air_date) <= currentDate; - } - return false; - }); + // filter out results that aren't movies or shows + const results = data.results.filter( + (r) => + r.media_type === TMDBContentTypes.MOVIE || + r.media_type === TMDBContentTypes.TV, + ); return results; } From 4386772404de469d3980252fc38aa327a95846a6 Mon Sep 17 00:00:00 2001 From: Honkertonken <94032937+Honkertonken@users.noreply.github.com> Date: Thu, 28 Mar 2024 13:27:05 +0530 Subject: [PATCH 16/20] Update MediaCard.tsx --- src/components/media/MediaCard.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/media/MediaCard.tsx b/src/components/media/MediaCard.tsx index ac6c243c..ce4eeffe 100644 --- a/src/components/media/MediaCard.tsx +++ b/src/components/media/MediaCard.tsx @@ -35,13 +35,15 @@ function MediaCardContent({ const { t } = useTranslation(); const percentageString = `${Math.round(percentage ?? 0).toFixed(0)}%`; - const canLink = linkable && !closable && !!media.year; + const currentYear = new Date().getFullYear(); + const isReleased = media.year && media.year < currentYear; + const canLink = linkable && !closable && isReleased; const dotListContent = [t(`media.types.${media.type}`)]; - if (media.year) { + if (isReleased) { dotListContent.push(media.year.toFixed()); } else { - dotListContent.push(t("Unreleased")); + dotListContent.push(t("media.unreleased")); } return ( @@ -146,7 +148,9 @@ function MediaCardContent({ export function MediaCard(props: MediaCardProps) { const content = ; - const canLink = props.linkable && !props.closable; + const currentYear = new Date().getFullYear(); + const isReleased = props.media.year && props.media.year < currentYear; + const canLink = props.linkable && !props.closable && isReleased; let link = canLink ? `/media/${encodeURIComponent(mediaItemToId(props.media))}` @@ -161,7 +165,7 @@ export function MediaCard(props: MediaCardProps) { } } - if (!props.linkable || !props.media.year) return {content}; + if (!canLink) return {content}; return ( Date: Thu, 28 Mar 2024 13:36:56 +0530 Subject: [PATCH 17/20] Update MediaCard.tsx --- src/components/media/MediaCard.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/media/MediaCard.tsx b/src/components/media/MediaCard.tsx index ce4eeffe..937ff1c6 100644 --- a/src/components/media/MediaCard.tsx +++ b/src/components/media/MediaCard.tsx @@ -36,11 +36,11 @@ function MediaCardContent({ const percentageString = `${Math.round(percentage ?? 0).toFixed(0)}%`; const currentYear = new Date().getFullYear(); - const isReleased = media.year && media.year < currentYear; - const canLink = linkable && !closable && isReleased; + const canLink = + linkable && !closable && media.year && media.year < currentYear; const dotListContent = [t(`media.types.${media.type}`)]; - if (isReleased) { + if (media.year && media.year < currentYear) { dotListContent.push(media.year.toFixed()); } else { dotListContent.push(t("media.unreleased")); @@ -149,8 +149,11 @@ export function MediaCard(props: MediaCardProps) { const content = ; const currentYear = new Date().getFullYear(); - const isReleased = props.media.year && props.media.year < currentYear; - const canLink = props.linkable && !props.closable && isReleased; + const canLink = + props.linkable && + !props.closable && + props.media.year && + props.media.year < currentYear; let link = canLink ? `/media/${encodeURIComponent(mediaItemToId(props.media))}` From d82de1f7c89c214ec983bc244f4f1d3381410342 Mon Sep 17 00:00:00 2001 From: Captain Jack Sparrow <163903675+sussy-code@users.noreply.github.com> Date: Thu, 28 Mar 2024 05:17:17 -0400 Subject: [PATCH 18/20] Better scrape error text for extension (#1042) * Add better scrape error messages for the extension * Remove config.js silly me * Polish and resolve issues * Update src/pages/parts/player/ScrapeErrorPart.tsx Co-authored-by: William Oldham * Update src/pages/parts/player/ScrapeErrorPart.tsx Co-authored-by: William Oldham * Update src/pages/parts/player/ScrapeErrorPart.tsx Co-authored-by: William Oldham * Update src/pages/parts/player/ScrapeErrorPart.tsx Co-authored-by: William Oldham * Update src/pages/parts/player/ScrapeErrorPart.tsx Co-authored-by: William Oldham * Update src/pages/parts/player/ScrapeErrorPart.tsx Co-authored-by: William Oldham * Fix duplicate button value * Resolve issues * Ok now i fixed it all * Apply suggestions from code review * I am dum --------- Co-authored-by: Cooper Ransom Co-authored-by: William Oldham --- index.html | 2 +- src/assets/locales/en.json | 7 +++ src/pages/onboarding/OnboardingExtension.tsx | 23 +------- src/pages/parts/player/ScrapeErrorPart.tsx | 61 +++++++++++++++++++- src/utils/extension.ts | 20 +++++++ 5 files changed, 90 insertions(+), 23 deletions(-) create mode 100644 src/utils/extension.ts diff --git a/index.html b/index.html index 1d1c3577..4555b17a 100644 --- a/index.html +++ b/index.html @@ -162,4 +162,4 @@ - \ No newline at end of file + diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index eeda3629..ab128219 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -388,6 +388,13 @@ "homeButton": "Go home", "text": "We have searched through our providers and cannot find the media you are looking for! We do not host the media and have no control over what is available. Please click 'Show details' below for more details.", "title": "We couldn't find that" + }, + "extensionFailure": { + "badge": "Extension disabled", + "homeButton": "Go home", + "enableExtension": "Enable extension", + "title": "Please enable the extension", + "text": "You've installed the movie-web extension. To start using it, you need to enable the extension for this site." } }, "time": { diff --git a/src/pages/onboarding/OnboardingExtension.tsx b/src/pages/onboarding/OnboardingExtension.tsx index db351dda..66e662e2 100644 --- a/src/pages/onboarding/OnboardingExtension.tsx +++ b/src/pages/onboarding/OnboardingExtension.tsx @@ -2,8 +2,7 @@ import { ReactNode, useCallback, useEffect, useMemo, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; import { useAsyncFn, useInterval } from "react-use"; -import { isAllowedExtensionVersion } from "@/backend/extension/compatibility"; -import { extensionInfo, sendPage } from "@/backend/extension/messaging"; +import { sendPage } from "@/backend/extension/messaging"; import { Button } from "@/components/buttons/Button"; import { Icon, Icons } from "@/components/Icon"; import { Loading } from "@/components/layout/Loading"; @@ -22,24 +21,8 @@ import { ExtensionDetectionResult, detectExtensionInstall, } from "@/utils/detectFeatures"; - -type ExtensionStatus = - | "unknown" - | "failed" - | "disallowed" - | "noperms" - | "outdated" - | "success"; - -async function getExtensionState(): Promise { - const info = await extensionInfo(); - if (!info) return "unknown"; // cant talk to extension - if (!info.success) return "failed"; // extension failed to respond - if (!info.allowed) return "disallowed"; // extension is not enabled on this page - if (!info.hasPermission) return "noperms"; // extension has no perms to do it's tasks - if (!isAllowedExtensionVersion(info.version)) return "outdated"; // extension is too old - return "success"; // no problems -} +import { getExtensionState } from "@/utils/extension"; +import type { ExtensionStatus } from "@/utils/extension"; function RefreshBar() { const { t } = useTranslation(); diff --git a/src/pages/parts/player/ScrapeErrorPart.tsx b/src/pages/parts/player/ScrapeErrorPart.tsx index 127a69a6..bc1f78e6 100644 --- a/src/pages/parts/player/ScrapeErrorPart.tsx +++ b/src/pages/parts/player/ScrapeErrorPart.tsx @@ -1,7 +1,8 @@ -import { useMemo } from "react"; -import { useTranslation } from "react-i18next"; +import { useEffect, useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; import { useLocation } from "react-router-dom"; +import { sendPage } from "@/backend/extension/messaging"; import { Button } from "@/components/buttons/Button"; import { Icons } from "@/components/Icon"; import { IconPill } from "@/components/layout/IconPill"; @@ -10,6 +11,8 @@ import { Paragraph } from "@/components/text/Paragraph"; import { Title } from "@/components/text/Title"; import { ScrapingItems, ScrapingSegment } from "@/hooks/useProviderScrape"; import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout"; +import { getExtensionState } from "@/utils/extension"; +import type { ExtensionStatus } from "@/utils/extension"; import { getProviderApiUrls } from "@/utils/proxyUrls"; import { ErrorCardInModal } from "../errors/ErrorCard"; @@ -25,6 +28,8 @@ export function ScrapeErrorPart(props: ScrapeErrorPartProps) { const { t } = useTranslation(); const modal = useModal("error"); const location = useLocation(); + const [extensionState, setExtensionState] = + useState("unknown"); const error = useMemo(() => { const data = props.data; @@ -42,6 +47,58 @@ export function ScrapeErrorPart(props: ScrapeErrorPartProps) { return str; }, [props, location]); + useEffect(() => { + getExtensionState().then((state: ExtensionStatus) => { + setExtensionState(state); + }); + }, [t]); + + if (extensionState === "disallowed") { + return ( + + + + {t("player.scraping.extensionFailure.badge")} + + {t("player.scraping.extensionFailure.title")} + + + ), + }} + /> + +
+ + +
+
+
+ ); + } + return ( diff --git a/src/utils/extension.ts b/src/utils/extension.ts new file mode 100644 index 00000000..8874146b --- /dev/null +++ b/src/utils/extension.ts @@ -0,0 +1,20 @@ +import { isAllowedExtensionVersion } from "@/backend/extension/compatibility"; +import { extensionInfo } from "@/backend/extension/messaging"; + +export type ExtensionStatus = + | "unknown" + | "failed" + | "disallowed" + | "noperms" + | "outdated" + | "success"; + +export async function getExtensionState(): Promise { + const info = await extensionInfo(); + if (!info) return "unknown"; // cant talk to extension + if (!info.success) return "failed"; // extension failed to respond + if (!info.allowed) return "disallowed"; // extension is not enabled on this page + if (!info.hasPermission) return "noperms"; // extension has no perms to do it's tasks + if (!isAllowedExtensionVersion(info.version)) return "outdated"; // extension is too old + return "success"; // no problems +} From bfee71f44620e4d66e338f0f7e86303a811916c1 Mon Sep 17 00:00:00 2001 From: Honkertonken <94032937+Honkertonken@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:47:18 +0530 Subject: [PATCH 19/20] Update MediaCard.tsx --- src/components/media/MediaCard.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/media/MediaCard.tsx b/src/components/media/MediaCard.tsx index 937ff1c6..563e4d9b 100644 --- a/src/components/media/MediaCard.tsx +++ b/src/components/media/MediaCard.tsx @@ -37,10 +37,12 @@ function MediaCardContent({ const currentYear = new Date().getFullYear(); const canLink = - linkable && !closable && media.year && media.year < currentYear; + linkable && !closable && media.year && media.year <= currentYear; const dotListContent = [t(`media.types.${media.type}`)]; - if (media.year && media.year < currentYear) { + if (media.year && media.year > currentYear) { + dotListContent.push(`${media.year}`, t("media.unreleased")); + } else if (media.year) { dotListContent.push(media.year.toFixed()); } else { dotListContent.push(t("media.unreleased")); @@ -153,7 +155,7 @@ export function MediaCard(props: MediaCardProps) { props.linkable && !props.closable && props.media.year && - props.media.year < currentYear; + props.media.year <= currentYear; let link = canLink ? `/media/${encodeURIComponent(mediaItemToId(props.media))}` From 20202d2216c216ed9dc3e854081c6c4b3b9aef62 Mon Sep 17 00:00:00 2001 From: William Oldham Date: Thu, 28 Mar 2024 13:39:18 +0000 Subject: [PATCH 20/20] Add logic for same year not released and fix language string --- src/assets/locales/en.json | 1 + src/backend/metadata/getmeta.ts | 4 +-- src/backend/metadata/tmdb.ts | 9 ++++--- src/backend/metadata/types/tmdb.ts | 2 +- src/components/media/MediaCard.tsx | 42 +++++++++++++++++++++--------- src/utils/mediaTypes.ts | 1 + 6 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index ab128219..bc042462 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -148,6 +148,7 @@ }, "media": { "episodeDisplay": "S{{season}} E{{episode}}", + "unreleased": "Unreleased", "types": { "movie": "Movie", "show": "Show" diff --git a/src/backend/metadata/getmeta.ts b/src/backend/metadata/getmeta.ts index add1089e..3da79ac2 100644 --- a/src/backend/metadata/getmeta.ts +++ b/src/backend/metadata/getmeta.ts @@ -43,7 +43,7 @@ export function formatTMDBMetaResult( title: movie.title, object_type: mediaTypeToTMDB(type), poster: getMediaPoster(movie.poster_path) ?? undefined, - original_release_year: new Date(movie.release_date).getFullYear(), + original_release_date: new Date(movie.release_date), }; } if (type === MWMediaType.SERIES) { @@ -58,7 +58,7 @@ export function formatTMDBMetaResult( title: v.name, })), poster: getMediaPoster(show.poster_path) ?? undefined, - original_release_year: new Date(show.first_air_date).getFullYear(), + original_release_date: new Date(show.first_air_date), }; } diff --git a/src/backend/metadata/tmdb.ts b/src/backend/metadata/tmdb.ts index b143b312..67c7d56f 100644 --- a/src/backend/metadata/tmdb.ts +++ b/src/backend/metadata/tmdb.ts @@ -66,7 +66,7 @@ export function formatTMDBMeta( return { title: media.title, id: media.id.toString(), - year: media.original_release_year?.toString(), + year: media.original_release_date?.getFullYear()?.toString(), poster: media.poster, type, seasons: seasons as any, @@ -94,7 +94,8 @@ export function formatTMDBMetaToMediaItem(media: TMDBMediaResult): MediaItem { return { title: media.title, id: media.id.toString(), - year: media.original_release_year ?? 0, + year: media.original_release_date?.getFullYear() ?? 0, + release_date: media.original_release_date, poster: media.poster, type, }; @@ -260,7 +261,7 @@ export function formatTMDBSearchResult( title: show.name, poster: getMediaPoster(show.poster_path), id: show.id, - original_release_year: new Date(show.first_air_date).getFullYear(), + original_release_date: new Date(show.first_air_date), object_type: mediatype, }; } @@ -271,7 +272,7 @@ export function formatTMDBSearchResult( title: movie.title, poster: getMediaPoster(movie.poster_path), id: movie.id, - original_release_year: new Date(movie.release_date).getFullYear(), + original_release_date: new Date(movie.release_date), object_type: mediatype, }; } diff --git a/src/backend/metadata/types/tmdb.ts b/src/backend/metadata/types/tmdb.ts index 1071d96c..5d082f55 100644 --- a/src/backend/metadata/types/tmdb.ts +++ b/src/backend/metadata/types/tmdb.ts @@ -20,7 +20,7 @@ export type TMDBMediaResult = { title: string; poster?: string; id: number; - original_release_year?: number; + original_release_date?: Date; object_type: TMDBContentTypes; seasons?: TMDBSeasonShort[]; }; diff --git a/src/components/media/MediaCard.tsx b/src/components/media/MediaCard.tsx index 563e4d9b..17ada085 100644 --- a/src/components/media/MediaCard.tsx +++ b/src/components/media/MediaCard.tsx @@ -1,4 +1,5 @@ import classNames from "classnames"; +import { useCallback } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; @@ -24,6 +25,20 @@ export interface MediaCardProps { onClose?: () => void; } +function checkReleased(media: MediaItem): boolean { + const isReleasedYear = Boolean( + media.year && media.year <= new Date().getFullYear(), + ); + const isReleasedDate = Boolean( + media.release_date && media.release_date <= new Date(), + ); + + // If the media has a release date, use that, otherwise use the year + const isReleased = media.release_date ? isReleasedDate : isReleasedYear; + + return isReleased; +} + function MediaCardContent({ media, linkable, @@ -35,16 +50,17 @@ function MediaCardContent({ const { t } = useTranslation(); const percentageString = `${Math.round(percentage ?? 0).toFixed(0)}%`; - const currentYear = new Date().getFullYear(); - const canLink = - linkable && !closable && media.year && media.year <= currentYear; + const isReleased = useCallback(() => checkReleased(media), [media]); + + const canLink = linkable && !closable && isReleased(); const dotListContent = [t(`media.types.${media.type}`)]; - if (media.year && media.year > currentYear) { - dotListContent.push(`${media.year}`, t("media.unreleased")); - } else if (media.year) { + + if (media.year) { dotListContent.push(media.year.toFixed()); - } else { + } + + if (!isReleased()) { dotListContent.push(t("media.unreleased")); } @@ -150,12 +166,12 @@ function MediaCardContent({ export function MediaCard(props: MediaCardProps) { const content = ; - const currentYear = new Date().getFullYear(); - const canLink = - props.linkable && - !props.closable && - props.media.year && - props.media.year <= currentYear; + const isReleased = useCallback( + () => checkReleased(props.media), + [props.media], + ); + + const canLink = props.linkable && !props.closable && isReleased(); let link = canLink ? `/media/${encodeURIComponent(mediaItemToId(props.media))}` diff --git a/src/utils/mediaTypes.ts b/src/utils/mediaTypes.ts index f577ca5f..c81b1ac0 100644 --- a/src/utils/mediaTypes.ts +++ b/src/utils/mediaTypes.ts @@ -2,6 +2,7 @@ export interface MediaItem { id: string; title: string; year?: number; + release_date?: Date; poster?: string; type: "show" | "movie"; }