pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/

Revert "feat: hide the arrow buttons on scroll lists when at either end of the list (#61)"

This reverts commit 598f752b120019608af520f19b231e2b1afaa679.

Pas 467c4ea2 598f752b

+74 -248
+18 -55
src/components/overlays/detailsModal/components/carousels/EpisodeCarousel.tsx
··· 45 45 const updateItem = useProgressStore((s) => s.updateItem); 46 46 const confirmModal = useModal("season-watch-confirm"); 47 47 48 - const [canScrollLeft, setCanScrollLeft] = useState(false); 49 - const [canScrollRight, setCanScrollRight] = useState(false); 50 - 51 - const updateScrollState = () => { 52 - if (!carouselRef.current) { 53 - setCanScrollLeft(false); 54 - setCanScrollRight(false); 55 - return; 56 - } 57 - 58 - const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current; 59 - const isAtStart = scrollLeft <= 1; 60 - const isAtEnd = scrollLeft + clientWidth >= scrollWidth - 1; 61 - 62 - setCanScrollLeft(!isAtStart); 63 - setCanScrollRight(!isAtEnd); 64 - }; 65 - 66 - useEffect(() => { 67 - const carousel = carouselRef.current; 68 - if (!carousel) return; 69 - 70 - updateScrollState(); 71 - 72 - carousel.addEventListener("scroll", updateScrollState); 73 - window.addEventListener("resize", updateScrollState); 74 - 75 - return () => { 76 - carousel.removeEventListener("scroll", updateScrollState); 77 - window.removeEventListener("resize", updateScrollState); 78 - }; 79 - }, []); 80 - 81 48 const handleScroll = (direction: "left" | "right") => { 82 49 if (!carouselRef.current) return; 83 50 ··· 563 530 {/* Episodes Carousel */} 564 531 <div className="relative"> 565 532 {/* Left scroll button */} 566 - {canScrollLeft && ( 567 - <div className="absolute left-0 top-1/2 transform -translate-y-1/2 z-10 px-4 hidden lg:block"> 568 - <button 569 - type="button" 570 - className="p-2 bg-black/80 hover:bg-video-context-hoverColor transition-colors rounded-full border border-video-context-border backdrop-blur-sm" 571 - onClick={() => handleScroll("left")} 572 - > 573 - <Icon icon={Icons.CHEVRON_LEFT} className="text-white/80" /> 574 - </button> 575 - </div> 576 - )} 533 + <div className="absolute left-0 top-1/2 transform -translate-y-1/2 z-10 px-4 hidden lg:block"> 534 + <button 535 + type="button" 536 + className="p-2 bg-black/80 hover:bg-video-context-hoverColor transition-colors rounded-full border border-video-context-border backdrop-blur-sm" 537 + onClick={() => handleScroll("left")} 538 + > 539 + <Icon icon={Icons.CHEVRON_LEFT} className="text-white/80" /> 540 + </button> 541 + </div> 577 542 578 543 <div 579 544 ref={carouselRef} ··· 818 783 </div> 819 784 820 785 {/* Right scroll button */} 821 - {canScrollRight && ( 822 - <div className="absolute right-0 top-1/2 transform -translate-y-1/2 z-10 px-4 hidden lg:block"> 823 - <button 824 - type="button" 825 - className="p-2 bg-black/80 hover:bg-video-context-hoverColor transition-colors rounded-full border border-video-context-border backdrop-blur-sm" 826 - onClick={() => handleScroll("right")} 827 - > 828 - <Icon icon={Icons.CHEVRON_RIGHT} className="text-white/80" /> 829 - </button> 830 - </div> 831 - )} 786 + <div className="absolute right-0 top-1/2 transform -translate-y-1/2 z-10 px-4 hidden lg:block"> 787 + <button 788 + type="button" 789 + className="p-2 bg-black/80 hover:bg-video-context-hoverColor transition-colors rounded-full border border-video-context-border backdrop-blur-sm" 790 + onClick={() => handleScroll("right")} 791 + > 792 + <Icon icon={Icons.CHEVRON_RIGHT} className="text-white/80" /> 793 + </button> 794 + </div> 832 795 </div> 833 796 </div> 834 797 );
+26 -63
src/components/player/atoms/Episodes.tsx
··· 766 766 ], 767 767 ); 768 768 769 - const [canScrollLeft, setCanScrollLeft] = useState(false); 770 - const [canScrollRight, setCanScrollRight] = useState(false); 771 - 772 - const updateScrollState = () => { 773 - if (!carouselRef.current) { 774 - setCanScrollLeft(false); 775 - setCanScrollRight(false); 776 - return; 777 - } 778 - 779 - const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current; 780 - const isAtStart = scrollLeft <= 1; 781 - const isAtEnd = scrollLeft + clientWidth >= scrollWidth - 1; 782 - 783 - setCanScrollLeft(!isAtStart); 784 - setCanScrollRight(!isAtEnd); 785 - }; 786 - 787 - useEffect(() => { 788 - const carousel = carouselRef.current; 789 - if (!carousel) return; 790 - 791 - updateScrollState(); 792 - 793 - carousel.addEventListener("scroll", updateScrollState); 794 - window.addEventListener("resize", updateScrollState); 795 - 796 - return () => { 797 - carousel.removeEventListener("scroll", updateScrollState); 798 - window.removeEventListener("resize", updateScrollState); 799 - }; 800 - }, []); 801 - 802 769 const handleScroll = (direction: "left" | "right") => { 803 770 if (!carouselRef.current) return; 804 771 ··· 949 916 content = ( 950 917 <div className="relative"> 951 918 {/* Horizontal scroll buttons */} 952 - {canScrollLeft && ( 953 - <div 954 - className={classNames( 955 - "absolute left-0 top-1/2 transform -translate-y-1/2 z-10 px-4", 956 - forceCompactEpisodeView ? "hidden" : "hidden lg:block", 957 - )} 919 + <div 920 + className={classNames( 921 + "absolute left-0 top-1/2 transform -translate-y-1/2 z-10 px-4", 922 + forceCompactEpisodeView ? "hidden" : "hidden lg:block", 923 + )} 924 + > 925 + <button 926 + type="button" 927 + className="p-2 bg-black/80 hover:bg-video-context-hoverColor transition-colors rounded-full border border-video-context-border backdrop-blur-sm" 928 + onClick={() => handleScroll("left")} 958 929 > 959 - <button 960 - type="button" 961 - className="p-2 bg-black/80 hover:bg-video-context-hoverColor transition-colors rounded-full border border-video-context-border backdrop-blur-sm" 962 - onClick={() => handleScroll("left")} 963 - > 964 - <Icon icon={Icons.CHEVRON_LEFT} className="text-white/80" /> 965 - </button> 966 - </div> 967 - )} 930 + <Icon icon={Icons.CHEVRON_LEFT} className="text-white/80" /> 931 + </button> 932 + </div> 968 933 969 934 <div 970 935 ref={carouselRef} ··· 1031 996 </div> 1032 997 1033 998 {/* Right scroll button */} 1034 - {canScrollRight && ( 1035 - <div 1036 - className={classNames( 1037 - "absolute right-0 top-1/2 transform -translate-y-1/2 z-10 px-4", 1038 - forceCompactEpisodeView ? "hidden" : "hidden lg:block", 1039 - )} 999 + <div 1000 + className={classNames( 1001 + "absolute right-0 top-1/2 transform -translate-y-1/2 z-10 px-4", 1002 + forceCompactEpisodeView ? "hidden" : "hidden lg:block", 1003 + )} 1004 + > 1005 + <button 1006 + type="button" 1007 + className="p-2 bg-black/80 hover:bg-video-context-hoverColor transition-colors rounded-full border border-video-context-border backdrop-blur-sm" 1008 + onClick={() => handleScroll("right")} 1040 1009 > 1041 - <button 1042 - type="button" 1043 - className="p-2 bg-black/80 hover:bg-video-context-hoverColor transition-colors rounded-full border border-video-context-border backdrop-blur-sm" 1044 - onClick={() => handleScroll("right")} 1045 - > 1046 - <Icon icon={Icons.CHEVRON_RIGHT} className="text-white/80" /> 1047 - </button> 1048 - </div> 1049 - )} 1010 + <Icon icon={Icons.CHEVRON_RIGHT} className="text-white/80" /> 1011 + </button> 1012 + </div> 1050 1013 </div> 1051 1014 ); 1052 1015 }
+3 -50
src/pages/discover/components/CarouselNavButtons.tsx
··· 1 - import { useCallback, useEffect, useState } from "react"; 2 - 3 1 import { Icon, Icons } from "@/components/Icon"; 4 2 import { Flare } from "@/components/utils/Flare"; 5 3 ··· 13 11 interface NavButtonProps { 14 12 direction: "left" | "right"; 15 13 onClick: () => void; 16 - visible: boolean; 17 14 } 18 15 19 - function NavButton({ direction, onClick, visible }: NavButtonProps) { 20 - if (!visible) return null; 21 - 16 + function NavButton({ direction, onClick }: NavButtonProps) { 22 17 return ( 23 18 <button 24 19 type="button" ··· 48 43 categorySlug, 49 44 carouselRefs, 50 45 }: CarouselNavButtonsProps) { 51 - const [canScrollLeft, setCanScrollLeft] = useState(false); 52 - const [canScrollRight, setCanScrollRight] = useState(false); 53 - 54 - const updateScrollState = useCallback(() => { 55 - const carousel = carouselRefs.current[categorySlug]; 56 - if (!carousel) { 57 - setCanScrollLeft(false); 58 - setCanScrollRight(false); 59 - return; 60 - } 61 - 62 - const { scrollLeft, scrollWidth, clientWidth } = carousel; 63 - const isAtStart = scrollLeft <= 1; 64 - const isAtEnd = scrollLeft + clientWidth >= scrollWidth - 1; 65 - 66 - setCanScrollLeft(!isAtStart); 67 - setCanScrollRight(!isAtEnd); 68 - }, [categorySlug, carouselRefs]); 69 - 70 - useEffect(() => { 71 - const carousel = carouselRefs.current[categorySlug]; 72 - if (!carousel) return; 73 - 74 - updateScrollState(); 75 - 76 - carousel.addEventListener("scroll", updateScrollState); 77 - window.addEventListener("resize", updateScrollState); 78 - 79 - return () => { 80 - carousel.removeEventListener("scroll", updateScrollState); 81 - window.removeEventListener("resize", updateScrollState); 82 - }; 83 - }, [categorySlug, carouselRefs, updateScrollState]); 84 - 85 46 const handleScroll = (direction: "left" | "right") => { 86 47 const carousel = carouselRefs.current[categorySlug]; 87 48 if (!carousel) return; ··· 115 76 116 77 return ( 117 78 <> 118 - <NavButton 119 - direction="left" 120 - onClick={() => handleScroll("left")} 121 - visible={canScrollLeft} 122 - /> 123 - <NavButton 124 - direction="right" 125 - onClick={() => handleScroll("right")} 126 - visible={canScrollRight} 127 - /> 79 + <NavButton direction="left" onClick={() => handleScroll("left")} /> 80 + <NavButton direction="right" onClick={() => handleScroll("right")} /> 128 81 </> 129 82 ); 130 83 }
+26 -79
src/pages/discover/components/CategoryButtons.tsx
··· 1 - import { useCallback, useEffect, useState } from "react"; 2 - 3 1 import { Icon, Icons } from "@/components/Icon"; 4 2 5 3 interface CategoryButtonsProps { ··· 17 15 isMobile, 18 16 showAlwaysScroll, 19 17 }: CategoryButtonsProps) { 20 - const [canScrollLeft, setCanScrollLeft] = useState(false); 21 - const [canScrollRight, setCanScrollRight] = useState(false); 22 - 23 - const updateScrollState = useCallback(() => { 24 - const element = document.getElementById(`button-carousel-${categoryType}`); 25 - if (!element) { 26 - setCanScrollLeft(false); 27 - setCanScrollRight(false); 28 - return; 29 - } 30 - 31 - const { scrollLeft, scrollWidth, clientWidth } = element; 32 - const isAtStart = scrollLeft <= 1; 33 - const isAtEnd = scrollLeft + clientWidth >= scrollWidth - 1; 34 - 35 - setCanScrollLeft(!isAtStart); 36 - setCanScrollRight(!isAtEnd); 37 - }, [categoryType]); 38 - 39 - useEffect(() => { 40 - const element = document.getElementById(`button-carousel-${categoryType}`); 41 - if (!element) return; 42 - 43 - updateScrollState(); 44 - 45 - element.addEventListener("scroll", updateScrollState); 46 - window.addEventListener("resize", updateScrollState); 47 - 48 - return () => { 49 - element.removeEventListener("scroll", updateScrollState); 50 - window.removeEventListener("resize", updateScrollState); 51 - }; 52 - }, [categoryType, updateScrollState]); 53 - 54 - useEffect(() => { 55 - const timeoutId = setTimeout(() => { 56 - updateScrollState(); 57 - }, 0); 58 - return () => clearTimeout(timeoutId); 59 - }, [categories, categoryType, updateScrollState]); 60 - 61 - const renderScrollButton = (direction: "left" | "right") => { 62 - const shouldShow = direction === "left" ? canScrollLeft : canScrollRight; 63 - 64 - if (!shouldShow && !showAlwaysScroll && !isMobile) return null; 65 - 66 - return ( 67 - <div> 68 - <button 69 - type="button" 70 - className="flex items-center rounded-full px-4 text-white py-3" 71 - onClick={() => { 72 - const element = document.getElementById( 73 - `button-carousel-${categoryType}`, 74 - ); 75 - if (element) { 76 - element.scrollBy({ 77 - left: direction === "left" ? -200 : 200, 78 - behavior: "smooth", 79 - }); 80 - } 81 - }} 82 - > 83 - <Icon 84 - icon={ 85 - direction === "left" ? Icons.CHEVRON_LEFT : Icons.CHEVRON_RIGHT 86 - } 87 - className="text-2xl rtl:-scale-x-100" 88 - /> 89 - </button> 90 - </div> 91 - ); 92 - }; 18 + const renderScrollButton = (direction: "left" | "right") => ( 19 + <div> 20 + <button 21 + type="button" 22 + className="flex items-center rounded-full px-4 text-white py-3" 23 + onClick={() => { 24 + const element = document.getElementById( 25 + `button-carousel-${categoryType}`, 26 + ); 27 + if (element) { 28 + element.scrollBy({ 29 + left: direction === "left" ? -200 : 200, 30 + behavior: "smooth", 31 + }); 32 + } 33 + }} 34 + > 35 + <Icon 36 + icon={direction === "left" ? Icons.CHEVRON_LEFT : Icons.CHEVRON_RIGHT} 37 + className="text-2xl rtl:-scale-x-100" 38 + /> 39 + </button> 40 + </div> 41 + ); 93 42 94 43 return ( 95 44 <div className="flex overflow-x-auto"> 96 - {(showAlwaysScroll || isMobile || canScrollLeft) && 97 - renderScrollButton("left")} 45 + {(showAlwaysScroll || isMobile) && renderScrollButton("left")} 98 46 99 47 <div 100 48 id={`button-carousel-${categoryType}`} ··· 114 62 </div> 115 63 </div> 116 64 117 - {(showAlwaysScroll || isMobile || canScrollRight) && 118 - renderScrollButton("right")} 65 + {(showAlwaysScroll || isMobile) && renderScrollButton("right")} 119 66 </div> 120 67 ); 121 68 }
+1 -1
src/stores/__old/watched/store.ts
··· 1 1 import { useProgressStore } from "@/stores/progress"; 2 2 3 - import { createVersionedStore } from "../migrations"; 4 3 import { OldData, migrateV2Videos } from "./migrations/v2"; 5 4 import { migrateV3Videos } from "./migrations/v3"; 6 5 import { migrateV4Videos } from "./migrations/v4"; 7 6 import { WatchedStoreData } from "./types"; 7 + import { createVersionedStore } from "../migrations"; 8 8 9 9 export const VideoProgressStore = createVersionedStore<WatchedStoreData>() 10 10 .setKey("video-progress")