From a14b3755cf41963afe510356f93e4ce851e71577 Mon Sep 17 00:00:00 2001 From: zisra <100528712+zisra@users.noreply.github.com> Date: Thu, 13 Nov 2025 03:02:41 +0800 Subject: [PATCH 1/3] Handle backtick command earlier + Ignore commands when cmd/atl is pressed + prevent -0.0 as subtitle delay (#62) * Use 12 hour clock + box around time * Fix ref * Handle backtick earlier + Ignore commands when cmd/atl is pressed + prevent -0.0 as subtitle delay * Exclude thumbnail from fade effect * Bring back comments --- src/assets/css/index.css | 6 +++--- src/components/player/atoms/ProgressBar.tsx | 2 +- src/components/player/atoms/settings/CaptionsView.tsx | 2 +- src/setup/imageFadeIn.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/assets/css/index.css b/src/assets/css/index.css index f345dfcc..cf68b39e 100644 --- a/src/assets/css/index.css +++ b/src/assets/css/index.css @@ -429,17 +429,17 @@ input[type="range"].styled-slider.slider-progress::-ms-fill-lower { } /* Image fade-in on load */ -img:not([src=""]) { +img:not(.no-fade):not([src=""]) { opacity: 0; transition: opacity 0.8s ease-in-out; } /* Fade in when image has loaded class */ -img.loaded { +img.loaded:not(.no-fade) { opacity: 1; } /* For images that are already cached/loaded, show them immediately */ -img[complete]:not([src=""]) { +img[complete]:not(.no-fade):not([src=""]) { opacity: 1; } diff --git a/src/components/player/atoms/ProgressBar.tsx b/src/components/player/atoms/ProgressBar.tsx index 90b91dcc..0c15fd82 100644 --- a/src/components/player/atoms/ProgressBar.tsx +++ b/src/components/player/atoms/ProgressBar.tsx @@ -59,7 +59,7 @@ function ThumbnailDisplay(props: { at: number; show: boolean }) { {currentThumbnail && ( )}

diff --git a/src/components/player/atoms/settings/CaptionsView.tsx b/src/components/player/atoms/settings/CaptionsView.tsx index a9ff591c..16040b65 100644 --- a/src/components/player/atoms/settings/CaptionsView.tsx +++ b/src/components/player/atoms/settings/CaptionsView.tsx @@ -411,7 +411,7 @@ export function CaptionsView({ }; try { - await navigator.clipboard.writeText(JSON.stringify(copyData, null, 2)); + await navigator.clipboard.writeText(JSON.stringify(copyData)); // Could add a toast notification here if needed } catch (err) { console.error("Failed to copy subtitle data:", err); diff --git a/src/setup/imageFadeIn.ts b/src/setup/imageFadeIn.ts index 79e779d9..158f2031 100644 --- a/src/setup/imageFadeIn.ts +++ b/src/setup/imageFadeIn.ts @@ -5,7 +5,7 @@ export function initializeImageFadeIn() { // Handle images that are already loaded (cached) const handleExistingImages = () => { - const images = document.querySelectorAll("img:not(.loaded)"); + const images = document.querySelectorAll(`img:not(.no-fade):not([src=""]`); images.forEach((img) => { const htmlImg = img as HTMLImageElement; if (htmlImg.complete && htmlImg.naturalHeight !== 0) { @@ -35,7 +35,7 @@ export function initializeImageFadeIn() { // Also check periodically for images that might have loaded // This handles edge cases where the load event might not fire const checkInterval = setInterval(() => { - const images = document.querySelectorAll("img:not(.loaded)"); + const images = document.querySelectorAll(`img:not(.no-fade):not([src=""]`); if (images.length === 0) { clearInterval(checkInterval); return; From 598f752b120019608af520f19b231e2b1afaa679 Mon Sep 17 00:00:00 2001 From: Rj Manhas <117674421+RjManhas@users.noreply.github.com> Date: Wed, 12 Nov 2025 12:03:10 -0700 Subject: [PATCH 2/3] feat: hide the arrow buttons on scroll lists when at either end of the list (#61) --- .../components/carousels/EpisodeCarousel.tsx | 73 +++++++++--- src/components/player/atoms/Episodes.tsx | 89 ++++++++++----- .../components/CarouselNavButtons.tsx | 53 ++++++++- .../discover/components/CategoryButtons.tsx | 105 +++++++++++++----- src/stores/__old/watched/store.ts | 2 +- 5 files changed, 248 insertions(+), 74 deletions(-) diff --git a/src/components/overlays/detailsModal/components/carousels/EpisodeCarousel.tsx b/src/components/overlays/detailsModal/components/carousels/EpisodeCarousel.tsx index b6eeacb6..9fec2494 100644 --- a/src/components/overlays/detailsModal/components/carousels/EpisodeCarousel.tsx +++ b/src/components/overlays/detailsModal/components/carousels/EpisodeCarousel.tsx @@ -45,6 +45,39 @@ export function EpisodeCarousel({ const updateItem = useProgressStore((s) => s.updateItem); const confirmModal = useModal("season-watch-confirm"); + const [canScrollLeft, setCanScrollLeft] = useState(false); + const [canScrollRight, setCanScrollRight] = useState(false); + + const updateScrollState = () => { + if (!carouselRef.current) { + setCanScrollLeft(false); + setCanScrollRight(false); + return; + } + + const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current; + const isAtStart = scrollLeft <= 1; + const isAtEnd = scrollLeft + clientWidth >= scrollWidth - 1; + + setCanScrollLeft(!isAtStart); + setCanScrollRight(!isAtEnd); + }; + + useEffect(() => { + const carousel = carouselRef.current; + if (!carousel) return; + + updateScrollState(); + + carousel.addEventListener("scroll", updateScrollState); + window.addEventListener("resize", updateScrollState); + + return () => { + carousel.removeEventListener("scroll", updateScrollState); + window.removeEventListener("resize", updateScrollState); + }; + }, []); + const handleScroll = (direction: "left" | "right") => { if (!carouselRef.current) return; @@ -530,15 +563,17 @@ export function EpisodeCarousel({ {/* Episodes Carousel */}

{/* Left scroll button */} -
- -
+ {canScrollLeft && ( +
+ +
+ )}
{/* Right scroll button */} -
- -
+ {canScrollRight && ( +
+ +
+ )}
); diff --git a/src/components/player/atoms/Episodes.tsx b/src/components/player/atoms/Episodes.tsx index 2d081980..8b9bf424 100644 --- a/src/components/player/atoms/Episodes.tsx +++ b/src/components/player/atoms/Episodes.tsx @@ -766,6 +766,39 @@ export function EpisodesView({ ], ); + const [canScrollLeft, setCanScrollLeft] = useState(false); + const [canScrollRight, setCanScrollRight] = useState(false); + + const updateScrollState = () => { + if (!carouselRef.current) { + setCanScrollLeft(false); + setCanScrollRight(false); + return; + } + + const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current; + const isAtStart = scrollLeft <= 1; + const isAtEnd = scrollLeft + clientWidth >= scrollWidth - 1; + + setCanScrollLeft(!isAtStart); + setCanScrollRight(!isAtEnd); + }; + + useEffect(() => { + const carousel = carouselRef.current; + if (!carousel) return; + + updateScrollState(); + + carousel.addEventListener("scroll", updateScrollState); + window.addEventListener("resize", updateScrollState); + + return () => { + carousel.removeEventListener("scroll", updateScrollState); + window.removeEventListener("resize", updateScrollState); + }; + }, []); + const handleScroll = (direction: "left" | "right") => { if (!carouselRef.current) return; @@ -916,20 +949,22 @@ export function EpisodesView({ content = (
{/* Horizontal scroll buttons */} -
- -
+ +
+ )}
{/* Right scroll button */} -
- -
+ +
+ )} ); } diff --git a/src/pages/discover/components/CarouselNavButtons.tsx b/src/pages/discover/components/CarouselNavButtons.tsx index bd9d3261..24cbdae3 100644 --- a/src/pages/discover/components/CarouselNavButtons.tsx +++ b/src/pages/discover/components/CarouselNavButtons.tsx @@ -1,3 +1,5 @@ +import { useCallback, useEffect, useState } from "react"; + import { Icon, Icons } from "@/components/Icon"; import { Flare } from "@/components/utils/Flare"; @@ -11,9 +13,12 @@ interface CarouselNavButtonsProps { interface NavButtonProps { direction: "left" | "right"; onClick: () => void; + visible: boolean; } -function NavButton({ direction, onClick }: NavButtonProps) { +function NavButton({ direction, onClick, visible }: NavButtonProps) { + if (!visible) return null; + return ( - - ); + const [canScrollLeft, setCanScrollLeft] = useState(false); + const [canScrollRight, setCanScrollRight] = useState(false); + + const updateScrollState = useCallback(() => { + const element = document.getElementById(`button-carousel-${categoryType}`); + if (!element) { + setCanScrollLeft(false); + setCanScrollRight(false); + return; + } + + const { scrollLeft, scrollWidth, clientWidth } = element; + const isAtStart = scrollLeft <= 1; + const isAtEnd = scrollLeft + clientWidth >= scrollWidth - 1; + + setCanScrollLeft(!isAtStart); + setCanScrollRight(!isAtEnd); + }, [categoryType]); + + useEffect(() => { + const element = document.getElementById(`button-carousel-${categoryType}`); + if (!element) return; + + updateScrollState(); + + element.addEventListener("scroll", updateScrollState); + window.addEventListener("resize", updateScrollState); + + return () => { + element.removeEventListener("scroll", updateScrollState); + window.removeEventListener("resize", updateScrollState); + }; + }, [categoryType, updateScrollState]); + + useEffect(() => { + const timeoutId = setTimeout(() => { + updateScrollState(); + }, 0); + return () => clearTimeout(timeoutId); + }, [categories, categoryType, updateScrollState]); + + const renderScrollButton = (direction: "left" | "right") => { + const shouldShow = direction === "left" ? canScrollLeft : canScrollRight; + + if (!shouldShow && !showAlwaysScroll && !isMobile) return null; + + return ( +
+ +
+ ); + }; return (
- {(showAlwaysScroll || isMobile) && renderScrollButton("left")} + {(showAlwaysScroll || isMobile || canScrollLeft) && + renderScrollButton("left")} - {(showAlwaysScroll || isMobile) && renderScrollButton("right")} + {(showAlwaysScroll || isMobile || canScrollRight) && + renderScrollButton("right")}
); } diff --git a/src/stores/__old/watched/store.ts b/src/stores/__old/watched/store.ts index 7d5739ad..5bc2f6fe 100644 --- a/src/stores/__old/watched/store.ts +++ b/src/stores/__old/watched/store.ts @@ -1,10 +1,10 @@ import { useProgressStore } from "@/stores/progress"; +import { createVersionedStore } from "../migrations"; import { OldData, migrateV2Videos } from "./migrations/v2"; import { migrateV3Videos } from "./migrations/v3"; import { migrateV4Videos } from "./migrations/v4"; import { WatchedStoreData } from "./types"; -import { createVersionedStore } from "../migrations"; export const VideoProgressStore = createVersionedStore() .setKey("video-progress") From 467c4ea202ecb24aa10b2d7f49434d27c8f04301 Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Wed, 12 Nov 2025 12:07:57 -0700 Subject: [PATCH 3/3] Revert "feat: hide the arrow buttons on scroll lists when at either end of the list (#61)" This reverts commit 598f752b120019608af520f19b231e2b1afaa679. --- .../components/carousels/EpisodeCarousel.tsx | 73 +++--------- src/components/player/atoms/Episodes.tsx | 89 +++++---------- .../components/CarouselNavButtons.tsx | 53 +-------- .../discover/components/CategoryButtons.tsx | 105 +++++------------- src/stores/__old/watched/store.ts | 2 +- 5 files changed, 74 insertions(+), 248 deletions(-) diff --git a/src/components/overlays/detailsModal/components/carousels/EpisodeCarousel.tsx b/src/components/overlays/detailsModal/components/carousels/EpisodeCarousel.tsx index 9fec2494..b6eeacb6 100644 --- a/src/components/overlays/detailsModal/components/carousels/EpisodeCarousel.tsx +++ b/src/components/overlays/detailsModal/components/carousels/EpisodeCarousel.tsx @@ -45,39 +45,6 @@ export function EpisodeCarousel({ const updateItem = useProgressStore((s) => s.updateItem); const confirmModal = useModal("season-watch-confirm"); - const [canScrollLeft, setCanScrollLeft] = useState(false); - const [canScrollRight, setCanScrollRight] = useState(false); - - const updateScrollState = () => { - if (!carouselRef.current) { - setCanScrollLeft(false); - setCanScrollRight(false); - return; - } - - const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current; - const isAtStart = scrollLeft <= 1; - const isAtEnd = scrollLeft + clientWidth >= scrollWidth - 1; - - setCanScrollLeft(!isAtStart); - setCanScrollRight(!isAtEnd); - }; - - useEffect(() => { - const carousel = carouselRef.current; - if (!carousel) return; - - updateScrollState(); - - carousel.addEventListener("scroll", updateScrollState); - window.addEventListener("resize", updateScrollState); - - return () => { - carousel.removeEventListener("scroll", updateScrollState); - window.removeEventListener("resize", updateScrollState); - }; - }, []); - const handleScroll = (direction: "left" | "right") => { if (!carouselRef.current) return; @@ -563,17 +530,15 @@ export function EpisodeCarousel({ {/* Episodes Carousel */}
{/* Left scroll button */} - {canScrollLeft && ( -
- -
- )} +
+ +
{/* Right scroll button */} - {canScrollRight && ( -
- -
- )} +
+ +
); diff --git a/src/components/player/atoms/Episodes.tsx b/src/components/player/atoms/Episodes.tsx index 8b9bf424..2d081980 100644 --- a/src/components/player/atoms/Episodes.tsx +++ b/src/components/player/atoms/Episodes.tsx @@ -766,39 +766,6 @@ export function EpisodesView({ ], ); - const [canScrollLeft, setCanScrollLeft] = useState(false); - const [canScrollRight, setCanScrollRight] = useState(false); - - const updateScrollState = () => { - if (!carouselRef.current) { - setCanScrollLeft(false); - setCanScrollRight(false); - return; - } - - const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current; - const isAtStart = scrollLeft <= 1; - const isAtEnd = scrollLeft + clientWidth >= scrollWidth - 1; - - setCanScrollLeft(!isAtStart); - setCanScrollRight(!isAtEnd); - }; - - useEffect(() => { - const carousel = carouselRef.current; - if (!carousel) return; - - updateScrollState(); - - carousel.addEventListener("scroll", updateScrollState); - window.addEventListener("resize", updateScrollState); - - return () => { - carousel.removeEventListener("scroll", updateScrollState); - window.removeEventListener("resize", updateScrollState); - }; - }, []); - const handleScroll = (direction: "left" | "right") => { if (!carouselRef.current) return; @@ -949,22 +916,20 @@ export function EpisodesView({ content = (
{/* Horizontal scroll buttons */} - {canScrollLeft && ( -
+ -
- )} + + +
{/* Right scroll button */} - {canScrollRight && ( -
+ -
- )} + + +
); } diff --git a/src/pages/discover/components/CarouselNavButtons.tsx b/src/pages/discover/components/CarouselNavButtons.tsx index 24cbdae3..bd9d3261 100644 --- a/src/pages/discover/components/CarouselNavButtons.tsx +++ b/src/pages/discover/components/CarouselNavButtons.tsx @@ -1,5 +1,3 @@ -import { useCallback, useEffect, useState } from "react"; - import { Icon, Icons } from "@/components/Icon"; import { Flare } from "@/components/utils/Flare"; @@ -13,12 +11,9 @@ interface CarouselNavButtonsProps { interface NavButtonProps { direction: "left" | "right"; onClick: () => void; - visible: boolean; } -function NavButton({ direction, onClick, visible }: NavButtonProps) { - if (!visible) return null; - +function NavButton({ direction, onClick }: NavButtonProps) { return ( - - ); - }; + const renderScrollButton = (direction: "left" | "right") => ( +
+ +
+ ); return (
- {(showAlwaysScroll || isMobile || canScrollLeft) && - renderScrollButton("left")} + {(showAlwaysScroll || isMobile) && renderScrollButton("left")} - {(showAlwaysScroll || isMobile || canScrollRight) && - renderScrollButton("right")} + {(showAlwaysScroll || isMobile) && renderScrollButton("right")}
); } diff --git a/src/stores/__old/watched/store.ts b/src/stores/__old/watched/store.ts index 5bc2f6fe..7d5739ad 100644 --- a/src/stores/__old/watched/store.ts +++ b/src/stores/__old/watched/store.ts @@ -1,10 +1,10 @@ import { useProgressStore } from "@/stores/progress"; -import { createVersionedStore } from "../migrations"; import { OldData, migrateV2Videos } from "./migrations/v2"; import { migrateV3Videos } from "./migrations/v3"; import { migrateV4Videos } from "./migrations/v4"; import { WatchedStoreData } from "./types"; +import { createVersionedStore } from "../migrations"; export const VideoProgressStore = createVersionedStore() .setKey("video-progress")