Skip to content

Commit

Permalink
add playlists in Collapsible
Browse files Browse the repository at this point in the history
  • Loading branch information
Laassari committed May 29, 2024
1 parent 2fcd29e commit 8deae7b
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 12 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"dependencies": {
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-scroll-area": "^1.0.5",
Expand Down
5 changes: 3 additions & 2 deletions src/components/CreatePlaylistModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ import { DialogClose } from '@radix-ui/react-dialog'

type Props = {
children: React.ReactNode
onOpenChange?: (open: boolean) => void
}

type Video = {
title: string
videoId: string
}

export default function CreatePlaylistModal({ children }: Props) {
export default function CreatePlaylistModal({ children, onOpenChange }: Props) {
const closeBtnRef = useRef<HTMLButtonElement>()
const [videos, setVideos] = useState<Video[]>([])
const [loading, setLoading] = useState(true)
Expand Down Expand Up @@ -73,7 +74,7 @@ export default function CreatePlaylistModal({ children }: Props) {
}

return (
<Dialog>
<Dialog onOpenChange={onOpenChange}>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className="max-w-[calc(100%-20px)] md:max-w-[700px]">
<DialogHeader>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default function Navbar({ className }: Props) {
</NavLink>

<SettingsModal>
<Button variant="ghost">
<Button variant="ghost" size='icon'>
<Settings size={20} />
</Button>
</SettingsModal>
Expand Down
4 changes: 2 additions & 2 deletions src/components/hooks/useAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useCallback, useEffect, useState } from 'react'
export default function useAsync<T>(
callback: () => Promise<T>,
dependencies = []
): { loading: boolean; error: any; value: T } {
): { loading: boolean; error: any; value: T; refresh: () => void } {
const [loading, setLoading] = useState(true)
const [error, setError] = useState()
const [value, setValue] = useState<T>()
Expand All @@ -22,5 +22,5 @@ export default function useAsync<T>(
callbackMemoized()
}, [callbackMemoized])

return { loading, error, value }
return { loading, error, value, refresh: callbackMemoized }
}
9 changes: 9 additions & 0 deletions src/components/ui/collapsible.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"

const Collapsible = CollapsiblePrimitive.Root

const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger

const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent

export { Collapsible, CollapsibleTrigger, CollapsibleContent }
7 changes: 7 additions & 0 deletions src/lib/playlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ export async function getAllPlaylists() {

return playlists
}
export async function delPlaylist(name: string) {
return del(name)
}

export function playlistUrl(name: string) {
return `/playlists/${btoa(name)}`
Expand All @@ -52,6 +55,10 @@ async function set(key: string, val: any) {
return db.put(storeName, val, key)
}

async function del(key: string) {
return (await dbPromise).delete(storeName, key)
}

// async function get(key: string) {
// return (await dbPromise).get(storeName, key)
// }
80 changes: 73 additions & 7 deletions src/scenes/Playlists.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,42 @@
import CreatePlaylistModal from '@/components/CreatePlaylistModal'
import useAsync from '@/components/hooks/useAsync'
import { Button } from '@/components/ui/button'
import { getAllPlaylists, playlistUrl } from '@/lib/playlist'
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible'
import { toast } from '@/components/ui/use-toast'
import { delPlaylist, getAllPlaylists, playlistUrl } from '@/lib/playlist'
import { formatSeconds } from '@/lib/utils'
import { ChevronsUpDown, Edit, Trash } from 'lucide-react'
import { Link } from 'react-router-dom'
import { Fragment } from 'react/jsx-runtime'
import * as Sentry from '@sentry/react'

export default function Playlists() {
const { loading, value: playlists } = useAsync(() => getAllPlaylists())
const {
loading,
value: playlists,
refresh,
} = useAsync(() => getAllPlaylists())

async function removePlaylist(name: string) {
try {
await delPlaylist(name)
refresh()
toast({ title: 'Playlist removed' })
} catch (error) {
Sentry.captureException(error)
toast({ title: 'An error occurred!' })
}
}

return (
<main className="max-w-[700px] mx-auto">
<div className="flex justify-between items-center mb-5">
<h1>Playlists</h1>
<CreatePlaylistModal>
<CreatePlaylistModal onOpenChange={(_) => refresh()}>
<Button variant="outline">Create</Button>
</CreatePlaylistModal>
</div>
Expand All @@ -21,10 +46,51 @@ export default function Playlists() {
) : (
<div>
{playlists.map((p) => (
<div key={p.name}>
<Link to={playlistUrl(p.name)}>{p.name}</Link>: ({p.videos.length}
)
</div>
<Collapsible key={p.name} className="space-y-2">
<div className="flex items-center border mb-2 rounded p-2">
<Link to={playlistUrl(p.name)} className="flex-grow">
{p.name}: ({p.videos.length})
</Link>

<CollapsibleTrigger asChild>
<Button variant="ghost" size="icon" className="w-9 p-0">
<ChevronsUpDown className="h-4 w-4" />
</Button>
</CollapsibleTrigger>

<Button variant="ghost" size="icon">
<Edit className="h-4 w-4" />
</Button>

<Button
onClick={() => removePlaylist(p.name)}
variant="ghost"
className="hover:bg-red-500 hover:text-white"
size="icon"
>
<Trash className="h-4 w-4" />
</Button>
</div>

<CollapsibleContent className="space-y-2 pb-4 px-4">
{p.videos.map((v, idx) => (
<Fragment key={v.videoId}>
<div className="flex gap-2">
<img
src={v.thumbnails.at(-1).url}
alt={p.name}
className="h-12 w-12 rounded aspect-video object-cover"
/>
<div>
<p>{v.title}</p>
<p>{formatSeconds(+v.lengthSeconds)}</p>
</div>
</div>
{idx < p.videos.length - 1 && <hr />}
</Fragment>
))}
</CollapsibleContent>
</Collapsible>
))}
</div>
)}
Expand Down
15 changes: 15 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,21 @@
"@radix-ui/react-use-previous" "1.0.1"
"@radix-ui/react-use-size" "1.0.1"

"@radix-ui/react-collapsible@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-1.0.3.tgz#df0e22e7a025439f13f62d4e4a9e92c4a0df5b81"
integrity sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.1"
"@radix-ui/react-compose-refs" "1.0.1"
"@radix-ui/react-context" "1.0.1"
"@radix-ui/react-id" "1.0.1"
"@radix-ui/react-presence" "1.0.1"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-use-controllable-state" "1.0.1"
"@radix-ui/react-use-layout-effect" "1.0.1"

"@radix-ui/[email protected]":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.3.tgz#9595a66e09026187524a36c6e7e9c7d286469159"
Expand Down

0 comments on commit 8deae7b

Please sign in to comment.