Skip to content

Commit

Permalink
Desktop tracks
Browse files Browse the repository at this point in the history
  • Loading branch information
jalvarado91 committed Sep 4, 2024
1 parent 2732ce2 commit f407a24
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 80 deletions.
39 changes: 30 additions & 9 deletions src/client/EpisodesScreen/EpisodeModalSheet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,34 @@ function EpisodeSheetContent({ episodeId }: { episodeId: string }) {
</a>
<EpisodeSheetFavoriteToggle episodeId={episodeId} />
</div>
<EpisodeTracksList episodeId={episodeId} />
<div className="xs:slide-in-from-bottom-3 md:fade-in animate-in rounded-lg duration-600 relative mx-3">
<div className="absolute rounded-lg inset-0 bg-black/20"></div>
<EpisodeTracksList episodeId={episodeId} />
</div>
<br />
</div>
);
}

function EpisodeTracksList({ episodeId }: { episodeId: string }) {
export function useEpisodeTracks(episodeId: string) {
const { data, status } = trpc["episode.getTracks"].useQuery(
{
episodeId,
},
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
}
);

return {
data,
loaded: status === "success",
hasTracks: status === "success" && data.length > 0,
};
}

export function EpisodeTracksList({ episodeId }: { episodeId: string }) {
const progress = usePlayerProgress();
const playerActions = usePlayerActions();
const progressSecs = progress / 1000;
Expand Down Expand Up @@ -146,10 +167,10 @@ function EpisodeTracksList({ episodeId }: { episodeId: string }) {
}

return loaded && loadedData.length > 0 ? (
<div className="slide-in-from-bottom-3 animate-in duration-600">
<div className="xs:slide-in-from-bottom-3 md:fade-in xs:animate-in duration-600 relative">
<div className="py-1" />
<div className="py-4 rounded-lg text-white relative mx-4 bg-accent shadow border-accent">
<div className="absolute rounded-lg inset-0 bg-black/20"></div>
<div className="py-4 rounded-lg text-white relative border-accent">
{/* <div className="absolute rounded-lg inset-0 bg-black/20"></div> */}
<div className="relative px-4 font-bold text-white text-lg mb-4">
{loadedData.length} Tracks
</div>
Expand All @@ -159,15 +180,15 @@ function EpisodeTracksList({ episodeId }: { episodeId: string }) {
return (
<button
onClick={() => onTrackClick(t)}
className={cn("w-full relative")}
className={cn("w-full relative hover:bg-white/10")}
>
<div
data-current-track={isCurrent}
className={cn(
"absolute w-[2px] inset-y-0 bg-white opacity-0 fade-in-100 data-[current-track=true]:opacity-100 data-[current-track=true]:animate-in"
"absolute w-[2px] md:w-[4px] inset-y-0 bg-white opacity-0 fade-in-100 data-[current-track=true]:opacity-100 data-[current-track=true]:animate-in"
)}
></div>
<div className="space-x-5 relative flex w-full justify-between items-center px-4 py-2">
<div className="space-x-5 relative flex w-full justify-between items-center px-4 md:px-4 py-2">
<div className="flex text-left items-center space-x-3 relative w-full">
<div
className={cn(
Expand All @@ -184,7 +205,7 @@ function EpisodeTracksList({ episodeId }: { episodeId: string }) {
<div
className={cn(
"font-medium text-sm",
isCurrent && "!font-bold"
isCurrent && "!font-bold md:!font-black"
)}
>
{t.name}
Expand Down
196 changes: 125 additions & 71 deletions src/client/components/Episode.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React from "react";
import React, { useState } from "react";
import cx from "classnames";
import { formatDate, formatTimeSecs } from "../helpers";
import {
BarsArrowDown,
BarsArrowUp,
HeartFilled,
HeartOutline,
IconDotsHorizontal,
Expand All @@ -10,6 +12,11 @@ import {
import { usePlayEpisodeMutation } from "../EpisodesScreen/useEpisodesScreenState";
import { usePlayerPlaying } from "../EpisodesScreen/PlayerStore";
import { EpisodeProjection } from "@/server/router";
import {
EpisodeTracksList,
useEpisodeTracks,
} from "../EpisodesScreen/EpisodeModalSheet";
import { AnimatePresence, motion } from "framer-motion";

export type EpisodeProps = {
episode: EpisodeProjection;
Expand All @@ -20,6 +27,8 @@ export type EpisodeProps = {
} & React.HTMLAttributes<HTMLDivElement>;

export function Episode(props: EpisodeProps) {
const [showTracks, setShowTracks] = useState<boolean>(false);

const {
episode: episode,
selected: selected = false,
Expand All @@ -29,92 +38,137 @@ export function Episode(props: EpisodeProps) {
onOptionsClick = () => {},
} = props;

const { hasTracks, loaded: hasTracksLoaded } = useEpisodeTracks(episode.id);

return (
<div
data-episode-id={episode.id}
className="flex h-full w-full items-stretch"
>
<>
<div
onClick={onClick}
className={cx(
"flex w-full cursor-pointer items-center justify-between border border-transparent p-3 text-left md:rounded-lg",
"active:bg-slate-50 md:hover:border md:hover:border-gray-100 md:hover:bg-slate-50",
"group transition-colors duration-75 focus:outline-none"
)}
data-episode-id={episode.id}
className="flex h-full w-full items-stretch"
>
<div className="flex items-center justify-start text-left">
<div className="flex w-full items-center">
<div className="relative h-16 w-16 flex-shrink-0 overflow-hidden rounded-lg">
<img
loading={"lazy"}
className="h-full w-full bg-gray-200"
src={episode.artworkUrl}
alt={episode.name}
/>
{selected && <AlbumArtOverlay />}
</div>
<div className="ml-2 md:flex md:flex-col-reverse">
<div className="text-sm text-gray-700 md:text-base">
<span>{formatDate(episode.releasedAt)}</span>
<span className="mx-1 inline-block md:hidden">&bull;</span>
<span className="inline-block md:hidden">
{formatTimeSecs(episode.duration)}
</span>
<div
onClick={onClick}
className={cx(
"flex w-full cursor-pointer items-center justify-between border border-transparent p-3 text-left md:rounded-lg",
"active:bg-slate-50 md:hover:border md:hover:border-gray-100 md:hover:bg-slate-50",
"group transition-colors duration-75 focus:outline-none"
)}
>
<div className="flex items-center justify-start text-left">
<div className="flex w-full items-center">
<div className="relative h-16 w-16 flex-shrink-0 overflow-hidden rounded-lg">
<img
loading={"lazy"}
className="h-full w-full bg-gray-200"
src={episode.artworkUrl}
alt={episode.name}
/>
{selected && <AlbumArtOverlay />}
</div>
<div className="ml-2 md:flex md:flex-col-reverse">
<div className="text-sm text-gray-700 md:text-base">
<span>{formatDate(episode.releasedAt)}</span>
<span className="mx-1 inline-block md:hidden">&bull;</span>
<span className="inline-block md:hidden">
{formatTimeSecs(episode.duration)}
</span>
</div>
<div
className={cx(
"flex items-start space-x-[4px]",
"md:text-lg",
{
"text-accent": selected,
}
)}
>
{selected && (
<div className="shrink-0">
<PlayingAnimation />
</div>
)}
<div className="font-bold leading-tight">{episode.name}</div>
</div>
</div>
<div
className={cx("flex items-start space-x-[4px]", "md:text-lg", {
"text-accent": selected,
})}
</div>
</div>
<div className="hidden items-center justify-end space-x-8 md:flex">
<div className="w-full flex space-x-3">
{hasTracks && selected && (
<button
className={cx(
"inline-block rounded-full p-2",
"transition-all duration-200 ease-in-out opacity-0",
"focus:outline-none hover:bg-gray-200 group-hover:opacity-100",
showTracks && "!opacity-100"
)}
title={showTracks ? "Close tracks" : "View Tracks"}
onClick={(e) => {
e.stopPropagation();
setShowTracks((currentState) => !currentState);
}}
>
{showTracks ? (
<BarsArrowUp className="h-5 w-5 fill-current" />
) : (
<BarsArrowDown className="h-5 w-5 stroke-current" />
)}
</button>
)}

<button
className={cx(
"inline-block rounded-full p-2",
"transition-all duration-200 ease-in-out",
"hover:bg-gray-200",
"focus:outline-none",
"opacity-0 group-hover:opacity-100",
favorite && "text-accent opacity-100"
)}
title={favorite ? "Remove from favorites" : "Add to favorites"}
onClick={(e) => {
e.stopPropagation();
onFavoriteClick();
}}
>
{selected && (
<div className="shrink-0">
<PlayingAnimation />
</div>
{favorite ? (
<HeartFilled className="h-5 w-5 fill-current" />
) : (
<HeartOutline className="h-5 w-5 stroke-current" />
)}
<div className="font-bold leading-tight">{episode.name}</div>
</div>
</button>
</div>

<span className="ml-">{formatTimeSecs(episode.duration)}</span>
</div>
</div>
<div className="hidden w-28 items-center justify-between space-x-4 md:flex">
<div className="flex md:hidden">
<button
className={cx(
"inline-block rounded-full p-1",
"transition-all duration-200 ease-in-out",
"hover:bg-gray-200",
"focus:outline-none",
"opacity-0 group-hover:opacity-100",
favorite && "text-accent opacity-100"
"flex h-full w-full items-center pl-2 pr-3",
"active:bg-slate-50",
"focus:outline-none"
)}
title={favorite ? "Remove from favorites" : "Add to favorites"}
onClick={(e) => {
e.stopPropagation();
onFavoriteClick();
}}
onClick={() => onOptionsClick()}
>
{favorite ? (
<HeartFilled className="h-5 w-5 fill-current" />
) : (
<HeartOutline className="h-5 w-5 stroke-current" />
)}
<IconDotsHorizontal className="h-5 w-5 stroke-current " />
</button>

<span className="">{formatTimeSecs(episode.duration)}</span>
</div>
</div>
<div className="flex md:hidden">
<button
className={cx(
"flex h-full w-full items-center pl-2 pr-3",
"active:bg-slate-50",
"focus:outline-none"
)}
onClick={() => onOptionsClick()}
>
<IconDotsHorizontal className="h-5 w-5 stroke-current " />
</button>
</div>
</div>
<AnimatePresence>
{selected && showTracks && (
<motion.div
transition={{ type: "spring", mass: 0.15, duration: 0.02 }}
initial={{ height: 0 }}
animate={{ height: "auto" }}
exit={{ height: 0 }}
className="hidden md:flex max-h-[calc(100vh*0.6)] items-stretch origin-top bg-accent rounded-lg overflow-y-auto relative"
>
<EpisodeTracksList episodeId={episode.id} />
</motion.div>
)}
</AnimatePresence>
</>
);
}

Expand Down
38 changes: 38 additions & 0 deletions src/client/components/Icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,44 @@ export function HeartFilled(props: any) {
);
}

export function BarsArrowDown(props: any) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
{...props}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3 4.5h14.25M3 9h9.75M3 13.5h9.75m4.5-4.5v12m0 0-3.75-3.75M17.25 21 21 17.25"
/>
</svg>
);
}

export function BarsArrowUp(props: any) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
{...props}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3 4.5h14.25M3 9h9.75M3 13.5h5.25m5.25-.75L17.25 9m0 0L21 12.75M17.25 9v12"
/>
</svg>
);
}

export function IconPlay(props: any) {
return (
<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" {...props}>
Expand Down

1 comment on commit f407a24

@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-r306ppe5y-juan-alvarados-projects-746f4d3e.vercel.app

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

Please sign in to comment.