Skip to content

Commit

Permalink
Select current episode
Browse files Browse the repository at this point in the history
  • Loading branch information
jalvarado91 committed Sep 16, 2024
1 parent f407a24 commit 57060bb
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 41 deletions.
48 changes: 28 additions & 20 deletions src/client/EpisodesScreen/EpisodeList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
import React, { useRef, useEffect } from "react";
import React, { useRef, useImperativeHandle } from "react";

type EpisodeListProps = {
focusedEpisodeId?: string;
children: React.ReactElement;
};

export function EpisodeList({ focusedEpisodeId, children }: EpisodeListProps) {
export type EpisodeListHandle = {
focusEpisode: (episodeId: string) => void;
};

export const EpisodeList = React.forwardRef<
EpisodeListHandle,
EpisodeListProps
>(({ children }: EpisodeListProps, ref) => {
const episodeListRef = useRef<HTMLDivElement | null>(null);

useEffect(() => {
if (focusedEpisodeId) {
const episode = episodeListRef.current?.querySelector(
`[data-episode-id="${focusedEpisodeId}"]`
);
if (episode) {
episode.scrollIntoView({
block: "center",
});
} else {
episodeListRef.current?.scrollTo({
top: 0,
});
}
}
}, [focusedEpisodeId]);
useImperativeHandle(
ref,
() => {
return {
focusEpisode(episodeId: string) {
const episode = episodeListRef.current?.querySelector(
`[data-episode-id="${episodeId}"]`,
);
if (episode) {
episode.scrollIntoView({
block: "center",
});
}
},
};
},
[],
);

return (
<div
Expand All @@ -33,4 +41,4 @@ export function EpisodeList({ focusedEpisodeId, children }: EpisodeListProps) {
{children}
</div>
);
}
});
29 changes: 19 additions & 10 deletions src/client/EpisodesScreen/EpisodesScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import React, { useEffect, useMemo, useState, useTransition } from "react";
import React, {
useContext,
useEffect,
useMemo,
useState,
useTransition,
} from "react";
import Player, { USE_NEW_PLAYER } from "./Player";
import { ShuffleButton } from "../components/ShuffleButton";
import EpisodeListSpinner from "./EpisodeList/EpisodeListSpinner";
Expand Down Expand Up @@ -36,17 +42,18 @@ import { IconSearch } from "../components/Icons";
import { cn } from "@/lib/utils";
import { useCollectiveSelectStore, useNavbarStore } from "./Navbar";
import { EpisodeProjection } from "@/server/router";
import { EpisodeListContext } from "@/pages";

type Props = {
searchText: string;
};

export function EpisodesScreen({ searchText }: Props) {
const [selectedSection, setSelectedSection] = useState<"all" | "favorites">(
"all"
"all",
);
const [activeSection, setActiveSection] = useState<"all" | "favorites">(
"all"
"all",
);
const [isPending, startTransition] = useTransition();

Expand All @@ -56,9 +63,11 @@ export function EpisodesScreen({ searchText }: Props) {
const { data: episodes, error } = useEpisodes();

const loadPersistedCollective = useCollectiveSelectStore(
(s) => s.loadPersisted
(s) => s.loadPersisted,
);

const { ref: episodeListRef } = useContext(EpisodeListContext);

useEffect(() => {
loadPersistedCollective();
}, []);
Expand All @@ -72,7 +81,7 @@ export function EpisodesScreen({ searchText }: Props) {

const { addFavorite, removeFavorite } = useFavorites();
const setContextMenuEpisode = useEpisodeOptionsStore(
(state) => state.setEpisode
(state) => state.setEpisode,
);
const favoritesCount = useFavoritesCount();
const isFavoriteFast = useIsFavoriteFast();
Expand Down Expand Up @@ -111,7 +120,7 @@ export function EpisodesScreen({ searchText }: Props) {

const lowerCaseSearch = searchText.toLowerCase();
return eps.filter((episode) =>
episode.name.toLowerCase().includes(lowerCaseSearch)
episode.name.toLowerCase().includes(lowerCaseSearch),
);
}

Expand Down Expand Up @@ -145,10 +154,10 @@ export function EpisodesScreen({ searchText }: Props) {
<div
className={classNames(
"relative h-full overflow-scroll py-2 pb-safe-bottom",
currentEpisodeId && "mb-24"
currentEpisodeId && "mb-24",
)}
>
<EpisodeList focusedEpisodeId={currentEpisodeId}>
<EpisodeList ref={episodeListRef}>
<>
<EpisodeListHeader
rightContent={
Expand Down Expand Up @@ -203,7 +212,7 @@ export function EpisodesScreen({ searchText }: Props) {
"rounded-full",
"shadow-md",
"hidden focus:outline-none",
!searchOpen && "flex sm:hidden"
!searchOpen && "flex sm:hidden",
)}
>
<IconSearch className="h-5 w-5 fill-current" />
Expand Down Expand Up @@ -310,7 +319,7 @@ function setNavigatorMediaMetadata(episode: ReturnType<typeof useGetEpisode>) {
navigator.mediaSession.metadata = new MediaMetadata({
title: episode.name,
artist: `${prefixMap[episode.collectiveSlug]} ${formatDate(
episode.releasedAt
episode.releasedAt,
)}`,
artwork: [
{
Expand Down
22 changes: 15 additions & 7 deletions src/client/EpisodesScreen/Player/PlayerControls.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useContext, useState } from "react";
import { formatDate, formatTime } from "../../helpers";
import {
IconPause,
Expand All @@ -12,6 +12,7 @@ import cx from "classnames";
import { Slider } from "@/client/components/Slider";
import { motion } from "framer-motion";
import { EpisodeProjection } from "@/server/router";
import { EpisodeListContext } from "@/pages";

export type PlayerControlsProps = {
episode: EpisodeProjection;
Expand Down Expand Up @@ -52,6 +53,8 @@ export function PlayerControls({

const scrubberProgress = seeking ? seekPosition : progress;

const { focusEpisode } = useContext(EpisodeListContext);

return (
<div className={cx("grid grid-cols-3 gap-5 xl:grid-cols-10")}>
<div className="flex items-center space-x-3 xl:col-span-2 ">
Expand All @@ -63,7 +66,12 @@ export function PlayerControls({
/>
</div>
<div className="flex flex-col justify-center">
<div className="text-md font-bold leading-tight">{episode.name}</div>
<button
className="text-md text-left hover:underline font-bold leading-tight"
onClick={() => focusEpisode(episode.id)}
>
{episode.name}
</button>
<div className="text-md text-gray-700">
{formatDate(episode.releasedAt)}
</div>
Expand All @@ -80,7 +88,7 @@ export function PlayerControls({
"rounded-full bg-transparent p-2 text-gray-700",
"transition-all duration-200 ease-in-out",
"hover:text-gray-900",
"focus:bg-gray-200 focus:outline-none"
"focus:bg-gray-200 focus:outline-none",
)}
>
<IconBackThirty className="h-8 w-8 fill-current" />
Expand All @@ -95,7 +103,7 @@ export function PlayerControls({
"transition-all duration-200 ease-in-out",
"hover:bg-accent/90 hover:shadow-lg",
"focus:bg-accent/90 focus:outline-none",
loading && "cursor-not-allowed disabled:cursor-not-allowed"
loading && "cursor-not-allowed disabled:cursor-not-allowed",
)}
>
{loading ? (
Expand Down Expand Up @@ -135,7 +143,7 @@ export function PlayerControls({
"rounded-full bg-transparent p-2 text-gray-700",
"transition-all duration-200 ease-in-out",
"hover:text-gray-900",
"focus:bg-gray-200 focus:outline-none"
"focus:bg-gray-200 focus:outline-none",
)}
>
<IconSkipThirty className="h-8 w-8 fill-current" />
Expand Down Expand Up @@ -179,7 +187,7 @@ export function PlayerControls({
className={cx(
"inline-block rounded-full p-2",
"transition-all duration-200 ease-in-out",
"hover:bg-gray-200 "
"hover:bg-gray-200 ",
)}
title="Open in SoundCloud"
target="_blank"
Expand All @@ -194,7 +202,7 @@ export function PlayerControls({
"inline-block rounded-full p-1",
"transition-all duration-200 ease-in-out",
"hover:bg-gray-200",
"focus:outline-none"
"focus:outline-none",
)}
title={muted ? "Unmute" : "Mute"}
onClick={() => {
Expand Down
2 changes: 1 addition & 1 deletion src/client/EpisodesScreen/Player/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export default function Player({ currentEpisodeId }: PlayerProps) {
className={classNames(
"bg-white",
!showMini && "w-full border border-t-gray-200 bg-white px-3 py-3",
showMini && "h-0"
showMini && "h-0",
)}
>
{currentEpisode.source === "MIXCLOUD" && (
Expand Down
25 changes: 22 additions & 3 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
import { useState } from "react";
import { RefObject, createContext, useRef, useState } from "react";
import { EpisodesScreen } from "@/client/EpisodesScreen/EpisodesScreen";
import { useShortcutHandlers } from "@/client/useKeyboardHandlers";
import Navbar from "@/client/EpisodesScreen/Navbar";
import "react-spring-bottom-sheet/dist/style.css";
import { EpisodeOptionsModal } from "../client/EpisodesScreen/EpisodeOptionsModal";
import { motion, useScroll, useTransform } from "framer-motion";
import { EpisodeListHandle } from "@/client/EpisodesScreen/EpisodeList";

export type EpisodeListContext = {
ref: RefObject<EpisodeListHandle>;
focusEpisode: (episodeId: string) => void;
};

export const EpisodeListContext = createContext<EpisodeListContext>(
null as unknown as EpisodeListContext,
);

export default function Home() {
const [searchText, setSearchText] = useState("");

const episodeListContextRef = useRef<EpisodeListHandle>(null);
const focusEpisode = (episodeId: string) => {
if (episodeListContextRef.current) {
episodeListContextRef.current.focusEpisode(episodeId);
}
};

useShortcutHandlers();

const { scrollY } = useScroll();
const opacity = useTransform(scrollY, [0, 20], [0, 1]);

return (
<>
<EpisodeListContext.Provider
value={{ ref: episodeListContextRef, focusEpisode }}
>
<div className="h-full w-full text-gray-900">
<div className="h-15 fixed top-0 z-20 w-full bg-white">
<motion.div
Expand All @@ -33,6 +52,6 @@ export default function Home() {
<EpisodesScreen searchText={searchText} />
</div>
<EpisodeOptionsModal />
</>
</EpisodeListContext.Provider>
);
}

1 comment on commit 57060bb

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for soulector ready!

✅ Preview
https://soulector-gy1hl1jiu-juan-alvarados-projects-746f4d3e.vercel.app

Built with commit 57060bb.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.