Implement basic listing and creating playlist
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ChevronsUpDown, Music4 } from 'lucide-vue-next';
|
||||
import { ChevronsUpDown, Music4, Plus } from 'lucide-vue-next';
|
||||
import Frame from '@/components/ui/frame/Frame.vue';
|
||||
import Select from '@/components/ui/select/Select.vue';
|
||||
import SelectCustomTrigger from '@/components/ui/select/SelectCustomTrigger.vue';
|
||||
@ -10,26 +10,43 @@ import SelectSeparator from '@/components/ui/select/SelectSeparator.vue';
|
||||
import SelectGroup from '@/components/ui/select/SelectGroup.vue';
|
||||
import SelectItem from '@/components/ui/select/SelectItem.vue';
|
||||
import { usePlaylists } from '@/composeables/api/playlist-controller/playlist-controller';
|
||||
import Skeleton from '@/components/ui/skeleton/Skeleton.vue';
|
||||
import PlaylistsNotFound from '@/components/internal/playlists/select/PlaylistsNotFound.vue';
|
||||
import Button from '../ui/button/Button.vue';
|
||||
import PlaylistCreateDialog from './playlists/select/PlaylistCreateDialog.vue';
|
||||
|
||||
const {
|
||||
open: sidebarOpen,
|
||||
} = useSidebar()
|
||||
|
||||
const { isLoading, isError, error, data } = usePlaylists();
|
||||
|
||||
const selectedPlaylist = ref(-1);
|
||||
|
||||
watch(data, (value) => {
|
||||
const newValue = value?.data[0]?.id || -1;
|
||||
if (selectedPlaylist.value === -1) {
|
||||
selectedPlaylist.value = newValue;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select>
|
||||
<Select v-model="selectedPlaylist">
|
||||
<div v-if="sidebarOpen" class="hover:bg-sidebar-muted cursor-pointer rounded-md select-none">
|
||||
<SelectCustomTrigger class="w-full flex p-2 gap-2 items-center">
|
||||
<Frame borderRadius="round" background="primary" padding="dense" margin="none">
|
||||
<Music4 class="text-primary-foreground" :size="24" />
|
||||
</Frame>
|
||||
<div class="overflow-hidden text-start">
|
||||
<h4 class="text-xl font-semibold tracking-tight truncate">
|
||||
My playlist
|
||||
<Skeleton v-if="isLoading" class="w-32 h-5 rounded-full" />
|
||||
<h4 v-else-if="data" class="text-xl font-semibold tracking-tight truncate">
|
||||
<!-- TODO: i18n -->
|
||||
{{data.data.find(playlist => playlist.id === selectedPlaylist)?.title ||
|
||||
'No playlist selected'}}
|
||||
</h4>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
<!-- TODO: Actual track count -->
|
||||
11 track(s)
|
||||
</p>
|
||||
</div>
|
||||
@ -51,25 +68,26 @@ const { isLoading, isError, error, data } = usePlaylists();
|
||||
<UiSpinner />
|
||||
</SelectContent>
|
||||
<SelectContent class="w-full" v-else-if="isError">
|
||||
<SelectLabel>{{ error?.message }}</SelectLabel>
|
||||
<SelectLabel>{{ error }}</SelectLabel>
|
||||
</SelectContent>
|
||||
<SelectContent class="w-full" v-else-if="data">
|
||||
<SelectLabel>Playlists</SelectLabel>
|
||||
<SelectSeparator />
|
||||
<SelectGroup>
|
||||
<SelectItem value="test">
|
||||
<span>Test</span>
|
||||
</SelectItem>
|
||||
<SelectItem value="second">
|
||||
<span>Second</span>
|
||||
</SelectItem>
|
||||
<SelectItem value="third">
|
||||
<span>Third</span>
|
||||
</SelectItem>
|
||||
<SelectItem value="fourth">
|
||||
<span>Fourth</span>
|
||||
<SelectItem v-for="item in data.data" :key="item.id" :value="item.id || -1">
|
||||
<span>{{ item.title }}</span>
|
||||
</SelectItem>
|
||||
<PlaylistsNotFound v-if="data.data.length === 0" />
|
||||
</SelectGroup>
|
||||
<SelectSeparator v-if="data.data.length > 0" />
|
||||
<PlaylistCreateDialog v-if="data.data.length > 0">
|
||||
<template #trigger>
|
||||
<Button variant="outline" size="icon" class="w-full">
|
||||
<Plus />
|
||||
Create playlist
|
||||
</Button>
|
||||
</template>
|
||||
</PlaylistCreateDialog>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
@ -0,0 +1,97 @@
|
||||
<script setup lang="ts">
|
||||
import { toTypedSchema } from "@vee-validate/zod"
|
||||
import { useForm } from "vee-validate"
|
||||
import * as z from "zod"
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog'
|
||||
import Button from '~/components/ui/button/Button.vue';
|
||||
import Input from '~/components/ui/input/Input.vue';
|
||||
import { toast } from "vue-sonner"
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form"
|
||||
import { getPlaylistsQueryKey, useCreatePlaylist } from "~/composeables/api/playlist-controller/playlist-controller"
|
||||
import { useQueryClient } from "@tanstack/vue-query";
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
playlistName: z.string().min(2).max(50).default(''),
|
||||
}))
|
||||
|
||||
const { isFieldDirty, handleSubmit, resetForm } = useForm({
|
||||
validationSchema: formSchema,
|
||||
})
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate: createPlaylistMutation } = useCreatePlaylist({
|
||||
mutation: {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: getPlaylistsQueryKey() });
|
||||
toast('Successfully created playlist', {
|
||||
description: `Playlist created successfully`,
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Cannot create playlist', {
|
||||
description: 'Error occurred during playlist creation, please try again later.',
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit(async (values) => {
|
||||
const playlistName = values['playlistName'];
|
||||
open.value = false;
|
||||
createPlaylistMutation({
|
||||
data: {
|
||||
title: playlistName,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const open = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="open">
|
||||
<DialogTrigger as-child>
|
||||
<slot name="trigger">
|
||||
Create playlist
|
||||
</slot>
|
||||
</DialogTrigger>
|
||||
<DialogContent class="sm:max-w-[425px]">
|
||||
<form @submit.prevent="onSubmit">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit profile</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create name for your playlist.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="py-4">
|
||||
<FormField v-slot="{ componentField }" name="playlistName" :validate-on-blur="!isFieldDirty">
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="shadcn" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is your public display name.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit">
|
||||
Create
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { FileMusicIcon } from 'lucide-vue-next';
|
||||
import Button from '@/components/ui/button/Button.vue';
|
||||
import Empty from '@/components/ui/empty/Empty.vue';
|
||||
import EmptyContent from '@/components/ui/empty/EmptyContent.vue';
|
||||
import EmptyDescription from '@/components/ui/empty/EmptyDescription.vue';
|
||||
import EmptyHeader from '@/components/ui/empty/EmptyHeader.vue';
|
||||
import EmptyMedia from '@/components/ui/empty/EmptyMedia.vue';
|
||||
import EmptyTitle from '@/components/ui/empty/EmptyTitle.vue';
|
||||
import PlaylistCreateDialog from './PlaylistCreateDialog.vue';
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<FileMusicIcon />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>No playlists found</EmptyTitle>
|
||||
<EmptyDescription>No playlists found, create one right now!</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<PlaylistCreateDialog>
|
||||
<template #trigger>
|
||||
<Button>Create playlist</Button>
|
||||
</template>
|
||||
</PlaylistCreateDialog>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
</template>
|
||||
Reference in New Issue
Block a user