1.0.0 • Published 4 years ago
use-vue-hooks v1.0.0
use-vue-hooks
useAudioControls
AudioPlayer.vue
<template>
<span>
<audio ref="audioEl" :src="currentTrack.audioUrl" style="display: none" />
<teleport to="body">
<div class="player-container">
<div class="track-preview">
<div class="track-preview-progress">
<div class="track-preview-progress-track">
<div
class="track-preview-progress-current"
:style="{ width: percentPlayed + '%' }"
></div>
</div>
</div>
<div class="track-preview-wrapper">
<div class="image-thumbnail">
<img :src="currentTrack.imageUrl" class="currentTrack-image" />
</div>
<div class="track-info">
<span class="title">{{ currentTrack.title }}</span>
<span class="artist">{{ currentTrack.artist }}</span>
</div>
<div class="track-controls">
<button class="btn icon-inner" @click.stop="togglePlaying">
<span v-if="isPlaying">Pause</span>
<span v-else>Play</span>
</button>
<button @click="playNext" class="btn next-btn">PlayNext</button>
</div>
</div>
</div>
</div>
</teleport>
</span>
</template>
<script lang="ts">
import { defineComponent, computed, ref, watch } from 'vue'
import { useAudioControls } from 'use-vue-hooks'
const sampleTracks = [
{
id: '5de11a305a58b41df485e98a',
name: 'Ozuna - Dificil Olvidar',
audioUrl:
'https://res.cloudinary.com/wlopez/video/upload/v1575033391/vapemusic2/2019/10/Ozuna%20%E2%80%93%20Dif%C3%ADcil%20Olvidar.mp3/gkxjvzeulzhoy9l0mzz8.mp3',
imageUrl:
'https://res.cloudinary.com/wlopez/image/upload/v1575033390/vapemusic2/2019/10/Ozuna%20-%20Niviru%20Cover.jpg/Ozuna_-_Niviru_Cover_z7mtjj.jpg',
artist: 'Ozuna',
title: 'Dificil De Olvidar',
genre: 'Reggaeton',
},
{
id: '5e514f8e47f6b853d0439a89',
name: 'Reik Ft. Farruko & Camilo - Si Me Dices Que Si',
audioUrl:
'https://res.cloudinary.com/wlopez/video/upload/v1582387085/vapemusic2/2020/1/Farruko_-_Si_Me_Dices_Que_Si.mp3',
imageUrl:
'https://res.cloudinary.com/wlopez/image/upload/v1582385647/vapemusic2/2020/1/Si_Me_Dices_Que_Si.jpg',
artist: 'Reik Ft. Farruko & Camilo',
genre: 'Reggaeton',
title: 'Si Me Dices Que Si',
},
]
export default defineComponent({
name: 'AudioPlayer',
setup() {
const audioEl = ref<HTMLAudioElement | null>(null)
const currentIndex = ref(0)
const isPlaying = ref(false)
const percentPlayed = ref(0)
const currentTrack = computed(function () {
return sampleTracks[currentIndex.value]
})
const audioUrl = computed(function () {
return currentTrack.value.audioUrl
})
const { controls, state, audioTime, audioTimeLeft } = useAudioControls({
audioEl,
src: audioUrl,
autoplay: false,
loop: false,
})
function playNext() {
let index = currentIndex.value + 1
if (index > sampleTracks.length - 1) {
index = 0
}
currentIndex.value = index
}
watch(isPlaying, is => {
if (is) {
controls.play()
} else {
controls.pause()
}
})
function togglePlaying() {
if (isPlaying.value === true) {
isPlaying.value = false
} else {
isPlaying.value = true
}
}
function seekTo(to: number) {
if (percentPlayed.value !== to) {
controls.seek(Math.floor(to))
}
}
return {
currentTrack,
isPlaying,
percentPlayed,
audioEl,
audioTime,
audioTimeLeft,
togglePlaying,
seekTo,
playNext,
}
},
})
</script>
<style scoped lang="scss">
.player-container {
position: fixed;
bottom: 0px;
width: 100%;
z-index: 1000;
}
.btn {
outline: none;
border: none;
padding: 8px 6px;
margin: 9px;
border-radius: 3px;
min-width: 60px;
}
.next-btn {
background: #1b5ccd;
color: white;
cursor: pointer;
}
.icon-inner {
background: #3a6f13;
outline: none;
border: none;
color: white;
cursor: pointer;
}
.track-preview {
width: 100%;
height: 50px;
background-color: #222;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.track-preview-progress {
position: absolute;
top: 0;
left: 0;
right: 0;
}
.track-preview-progress-track {
position: relative;
height: 2px;
background-color: #aaa;
}
.track-preview-progress-current {
position: absolute;
z-index: 1;
width: 60%;
height: 2px;
background-color: #fff;
}
.track-preview-wrapper {
display: flex;
flex-direction: row;
justify-content: center;
}
.image-thumbnail {
border-radius: 0;
display: block;
width: 48px;
height: 48px;
}
.currentTrack-image {
margin-top: 2px;
width: 48px;
height: 48px;
max-width: 100%;
border: 0;
min-width: 100%;
}
.track-info {
flex: 1 1;
height: 50px;
color: #fff;
display: flex;
align-items: center;
padding: 0 8px;
}
.title {
color: #fff;
font-size: 14px;
margin-right: 3px;
}
.artist {
color: rgb(195, 195, 195);
font-size: 14px;
margin-left: 2px;
}
.track-controls {
color: #fff;
display: flex;
align-items: center;
flex-direction: row;
font-size: 36px;
}
</style>
useQuery
<script lang="ts">
import { useQuery } from 'use-vue-hooks'
import { inject } from 'vue'
import gql from 'graphql-tag'
import { ApolloClient } from 'apollo-boost'
const FEED_QUERY = gql`
query getFeed($type: FeedType!, $offset: Int, $limit: Int) {
currentUser {
login
}
feed(type: $type, offset: $offset, limit: $limit) {
id
# ...
}
}
`
export default {
props: ['type'],
setup(props) {
const client = inject('apollo') as ApolloClient<any>
const { result } = useQuery({
client: client,
query: FEED_QUERY,
variables: {
type: props.type,
},
})
return {
result,
}
},
}
</script>
createUseQuery
- Create Reusable hooks with type safety.
useSearchSongs.ts
import { gql } from 'apollo-boost'
import { createUseQuery, Maybe, Scalars } from 'use-vue-hooks'
export type SongResponse = {
__typename?: 'SongResponse'
songs: Array<Song>
totalCount: Scalars['Float']
}
export type Song = {
__typename?: 'Song'
id: Scalars['ID']
artist: Scalars['String']
title: Scalars['String']
genre: Scalars['String']
album?: Maybe<Scalars['String']>
imageUrl: Scalars['String']
audioUrl: Scalars['String']
createdAt?: Maybe<Scalars['DateTime']>
updatedAt?: Maybe<Scalars['DateTime']>
name: Scalars['String']
}
export type SongFragmentFragment = { __typename?: 'Song' } & Pick<
Song,
'name' | 'imageUrl' | 'audioUrl' | 'id' | 'artist' | 'title' | 'genre'
>
export type SearchSongsQuery = { __typename?: 'Query' } & {
searchSongs: { __typename?: 'SongResponse' } & Pick<
SongResponse,
'totalCount'
> & {
songs: Array<{ __typename?: 'Song' } & SongFragmentFragment>
}
}
export type SearchSongsQueryVariables = {
query: Scalars['String']
skip?: Maybe<Scalars['Int']>
limit?: Maybe<Scalars['Int']>
}
export const SearchSongsDocument = gql`
query SearchSongs($query: String!, $skip: Int, $limit: Int) {
searchSongs(query: $query, skip: $skip, limit: $limit) {
totalCount
songs {
name
imageUrl
audioUrl
id
artist
title
genre
}
}
}
`
export const [useSearchSongsQuery, useSearchSongsLazyQuery] = createUseQuery<
SearchSongsQuery,
SearchSongsQueryVariables
>(SearchSongsDocument)
Search.vue
<template>
<div class="search">
<form @submit.prevent="search" class="search-form">
<div class="form-control">
<div class="search-input">
<svg
width="20"
xmlns="http://www.w3.org/2000/svg"
class="icon"
viewBox="0 0 512 512"
>
<path
d="M464 428L339.92 303.9a160.48 160.48 0 0030.72-94.58C370.64 120.37 298.27 48 209.32 48S48 120.37 48 209.32s72.37 161.32 161.32 161.32a160.48 160.48 0 0094.58-30.72L428 464zM209.32 319.69a110.38 110.38 0 11110.37-110.37 110.5 110.5 0 01-110.37 110.37z"
></path>
</svg>
<input
type="text"
v-model="query"
placeholder="Song name, artist or album"
/>
</div>
</div>
</form>
<div v-if="result.length > 0" class="song-grid-container">
<ul class="song-grid">
<li v-for="song of result" :key="song.id" :song="song">
{{ song.title }}
</li>
</ul>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, inject, ref } from 'vue'
import { useSearchSongsLazyQuery, Song } from './useSearchSongs'
import { ApolloClient } from 'apollo-boost'
export default defineComponent({
setup() {
// eslint-disable-next-line
const apollo = inject('apollo') as ApolloClient<any>
const query = ref('')
const [
execQuery,
{
result: { data, loading },
},
] = useSearchSongsLazyQuery(apollo)
function search() {
execQuery({
query: query.value,
limit: 20,
})
}
const result = computed(function () {
if (data && data.value && data.value.searchSongs) {
return data.value.searchSongs.songs
}
return [] as Song[]
})
return {
search,
query,
result,
loading,
}
},
})
</script>
1.0.0
4 years ago