-
-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added creating and deleting playlists.
- Loading branch information
1 parent
da3efa3
commit 2db778f
Showing
16 changed files
with
362 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import {FormEvent, useEffect, useState} from "react" | ||
import {createPortal} from "react-dom" | ||
import {useTranslation} from "react-i18next" | ||
import axios, {AxiosResponse} from "axios" | ||
import {enqueueSnackbar} from "notistack" | ||
import {useAppDispatch, useAppSelector} from "../store/hooks" | ||
import {PodcastEpisode, setCreateInviteModalOpen, setInvites} from "../store/CommonSlice" | ||
import {apiURL} from "../utils/Utilities" | ||
import {CustomButtonPrimary} from "./CustomButtonPrimary" | ||
import {Heading2} from "./Heading2" | ||
import "material-symbols/outlined.css" | ||
import {PlaylistDto, PlaylistDtoPost, PlaylistItem} from "../models/Playlist"; | ||
import {setCreatePlaylistOpen, setPlaylist} from "../store/PlaylistSlice"; | ||
import {SubmitHandler, useFieldArray, useForm} from "react-hook-form"; | ||
import {EpisodeSearch} from "./EpisodeSearch"; | ||
import {DragEvent} from "react"; | ||
import {PlaylistData} from "./PlaylistData"; | ||
import {PlaylistSearchEpisode} from "./PlaylistSearchEpisode"; | ||
export const CreatePlaylistModal = () => { | ||
const dispatch = useAppDispatch() | ||
const playListOpen = useAppSelector(state=>state.playlist.createPlaylistOpen) | ||
const {t} = useTranslation() | ||
const playlists = useAppSelector(state=>state.playlist.playlist) | ||
const [playListDto, setPlayListDto] = useState<PlaylistDtoPost>({name: '', items: []}) | ||
const [items,setItems] = useState<PodcastEpisode[]>([]) | ||
const { formState: {}, handleSubmit, control} = useForm<PlaylistDto>({ | ||
defaultValues: { | ||
name: '', | ||
items: [] | ||
} | ||
}) | ||
const [stage,setStage] = useState<number>(0) | ||
|
||
const create_playlist:SubmitHandler<PlaylistDto> = (data)=>{ | ||
const itemsMappedToIDs = items.map(item=>{ | ||
return { | ||
episode: item.id | ||
} satisfies PlaylistItem | ||
}) | ||
axios.post(apiURL+"/playlist", { | ||
name: data.name, | ||
items: itemsMappedToIDs | ||
} satisfies PlaylistDtoPost) | ||
.then(()=>{ | ||
enqueueSnackbar(t('settings-saved'), {variant: "success"}) | ||
}) | ||
} | ||
|
||
|
||
return createPortal( | ||
<div aria-hidden="true" id="defaultModal" onClick={()=>dispatch(setCreatePlaylistOpen(false))} className={`grid place-items-center fixed inset-0 bg-[rgba(0,0,0,0.5)] backdrop-blur overflow-x-hidden overflow-y-auto z-30 ${playListOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'}`} tabIndex={-1}> | ||
|
||
{/* Modal */} | ||
<div className="relative bg-white max-w-5xl p-8 rounded-2xl shadow-[0_4px_16px_rgba(0,0,0,0.2)]" onClick={e=>e.stopPropagation()}> | ||
|
||
{/* Close button */} | ||
<button type="button" className="absolute top-4 right-4 bg-transparent" data-modal-toggle="defaultModal" onClick={()=>dispatch(setCreatePlaylistOpen(false))}> | ||
<span className="material-symbols-outlined text-stone-400 hover:text-stone-600">close</span> | ||
<span className="sr-only">{t('closeModal')}</span> | ||
</button> | ||
|
||
{/* Submit form for creating a playlist */} | ||
<form onSubmit={handleSubmit(create_playlist)}> | ||
|
||
<div className="mt-5 mb-5 "> | ||
<Heading2 className="mb-4">{t('add-playlist')}</Heading2> | ||
</div> | ||
|
||
{ | ||
stage === 0 && <PlaylistData control={control}/> | ||
} | ||
|
||
{ | ||
stage === 1 && | ||
<PlaylistSearchEpisode items={items} setItems={setItems}/> | ||
} | ||
|
||
<div className="flex"> | ||
<button type="button"> | ||
<span className={`material-symbols-outlined ${stage===0&&'opacity-60'} text-mustard-600`} onClick={()=>{stage>=1&&setStage(stage-1)}}>arrow_back</span> | ||
</button> | ||
<div className="flex-1"></div> | ||
<button type="button" onClick={()=>{stage<=1&&setStage(stage+1)}}> | ||
<span className={`material-symbols-outlined ${stage===2&&'opacity-60'} text-mustard-600`}>arrow_forward</span> | ||
</button> | ||
</div> | ||
|
||
{stage === 2 && | ||
<><CustomButtonPrimary type="submit" className="float-right" onClick={()=>{ | ||
axios.post(apiURL+'/playlist', playListDto) | ||
.then((v: AxiosResponse<PlaylistDto>)=>{ | ||
enqueueSnackbar(t('invite-created'), {variant: "success"}) | ||
dispatch(setPlaylist([...playlists,v.data])) | ||
dispatch(setCreatePlaylistOpen(false)) | ||
}) | ||
}}>{t('create-playlist')}</CustomButtonPrimary> | ||
<br/> | ||
</> | ||
} | ||
</form> | ||
</div> | ||
</div>, document.getElementById('modal')! | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import {CustomInput} from "./CustomInput"; | ||
import {Controller} from "react-hook-form"; | ||
import {useTranslation} from "react-i18next"; | ||
import {FC} from "react"; | ||
|
||
type PlaylistDataProps = { | ||
control: any | ||
} | ||
|
||
|
||
export const PlaylistData:FC<PlaylistDataProps> = ({control})=>{ | ||
const {t} = useTranslation() | ||
|
||
return <div className="grid grid-cols-1 xs:grid-cols-[1fr_auto] items-center gap-2 xs:gap-6 mb-10"> | ||
<fieldset className="xs:contents mb-4"> | ||
<label className="ml-2 text-sm text-stone-600" htmlFor="use-existing-filenames">{t('playlist-name')}</label> | ||
|
||
<div className="flex flex-col gap-2"> | ||
<div className="flex"> | ||
<Controller | ||
name="name" | ||
control={control} | ||
render={({ field: { name, onChange, value }}) => ( | ||
<CustomInput id="use-existing-filenames" className="border-gray-500 border-2" name={name} onChange={onChange} value ={value} /> | ||
)} /> | ||
|
||
</div> | ||
</div> | ||
</fieldset> | ||
</div> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import {EpisodeSearch} from "./EpisodeSearch"; | ||
import {Dispatch, DragEvent, FC, SetStateAction, useState} from "react"; | ||
import {PodcastEpisode} from "../store/CommonSlice"; | ||
import {useTranslation} from "react-i18next"; | ||
|
||
type PlaylistSearchEpisodeProps = { | ||
items: PodcastEpisode[], | ||
setItems: Dispatch<SetStateAction<PodcastEpisode[]>> | ||
} | ||
|
||
|
||
export const PlaylistSearchEpisode:FC<PlaylistSearchEpisodeProps> = ({setItems,items})=>{ | ||
const [itemCurrentlyDragged,setItemCurrentlyDragged] = useState<PodcastEpisode>() | ||
const {t} = useTranslation() | ||
|
||
const handleDragStart = (dragItem: PodcastEpisode, index: number, event: DragEvent<HTMLTableRowElement> )=>{ | ||
event.dataTransfer.setData("text/plain", index.toString()) | ||
setItemCurrentlyDragged(dragItem) | ||
} | ||
|
||
return <> | ||
<EpisodeSearch onClickResult={(e)=>{ | ||
setItems([...items, e]) | ||
}} classNameResults="max-h-[min(20rem,calc(100vh-3rem-3rem))]" | ||
showBlankState={false} /> | ||
<div className={`scrollbox-x`}> | ||
<table className="text-left text-sm text-stone-900 w-full"> | ||
<thead> | ||
<tr className="border-b border-stone-300"> | ||
<th scope="col" className="pr-2 py-3"> | ||
# | ||
</th> | ||
<th scope="col" className="px-2 py-3"> | ||
{t('role')} | ||
</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{items.map((item, index) => { | ||
return <tr draggable onDrop={e=>{ | ||
e.preventDefault() | ||
const dropIndex = index | ||
const dragIndex = parseInt(e.dataTransfer.getData("text/plain")) | ||
|
||
setItems((items)=>{ | ||
// @ts-ignore | ||
const newItems = [...items] | ||
const dragItem = newItems[dragIndex] | ||
newItems.splice(dragIndex, 1) | ||
newItems.splice(dropIndex, 0, dragItem) | ||
return newItems | ||
}) | ||
}} onDragOver={(e)=>item.id!=itemCurrentlyDragged?.id&&e.preventDefault()} onDragStart={e=>handleDragStart(item, index, e)}> | ||
<td> | ||
{index} | ||
</td> | ||
<td> | ||
{item.name} | ||
</td> | ||
</tr> | ||
})} | ||
</tbody> | ||
</table> | ||
</div> | ||
</> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import {PodcastEpisode} from "../store/CommonSlice"; | ||
|
||
export type PlaylistDto = { | ||
id: number, | ||
name: string, | ||
items: PodcastEpisode[] | ||
} | ||
|
||
export type PlaylistDtoPost = { | ||
name: string, | ||
items: PlaylistItem[] | ||
} | ||
|
||
export type PlaylistItem = { | ||
episode: number | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.