Skip to content

Commit

Permalink
Extract reusable VAudioCollection.vue (#2695)
Browse files Browse the repository at this point in the history
* Extract reusable VAudioCollection.vue

Co-authored-by: Zack Krida <[email protected]>

* Move analytics test to e2e

* Move the gap setting to the `ol` level

---------

Co-authored-by: Zack Krida <[email protected]>
  • Loading branch information
obulat and zackkrida authored Jul 28, 2023
1 parent a102809 commit 2b034f8
Show file tree
Hide file tree
Showing 15 changed files with 386 additions and 265 deletions.
66 changes: 10 additions & 56 deletions frontend/src/components/VAudioDetails/VRelatedAudio.vue
Original file line number Diff line number Diff line change
@@ -1,72 +1,41 @@
<template>
<aside
v-if="showRelated"
:aria-label="$t('audioDetails.relatedAudios').toString()"
>
<section v-if="showRelated">
<h2 class="heading-6 lg:heading-6 mb-6">
{{ $t("audioDetails.relatedAudios") }}
</h2>
<!-- Negative margin compensates for the `p-4` padding in row layout. -->
<ol
v-if="!fetchState.fetchingError"
:aria-label="$t('audioDetails.relatedAudios').toString()"
class="-mx-2 mb-12 flex flex-col gap-4 md:-mx-4"
>
<li v-for="audio in media" :key="audio.id">
<VAudioTrack
:audio="audio"
layout="row"
:size="audioTrackSize"
@mousedown="sendSelectSearchResultEvent(audio)"
@interacted="$emit('interacted', $event)"
/>
</li>
</ol>
<LoadingIcon
v-if="!fetchState.fetchingError"
v-show="fetchState.isFetching"
<VAudioCollection
:results="media"
:fetch-state="fetchState"
:is-related="true"
:collection-label="$t('audioDetails.relatedAudios').toString()"
class="mb-12"
/>
<p v-show="!!fetchState.fetchingError">
{{ $t("mediaDetails.relatedError") }}
</p>
</aside>
</section>
</template>

<script lang="ts">
import { computed, defineComponent, watch } from "vue"
import { useRoute } from "@nuxtjs/composition-api"
import { useUiStore } from "~/stores/ui"
import { useSearchStore } from "~/stores/search"
import { useRelatedMediaStore } from "~/stores/media/related-media"
import { useAnalytics } from "~/composables/use-analytics"
import { AUDIO } from "~/constants/media"
import { defineEvent } from "~/types/emits"
import type { AudioDetail } from "~/types/media"
import type { AudioInteractionData } from "~/types/analytics"
import LoadingIcon from "~/components/LoadingIcon.vue"
import VAudioTrack from "~/components/VAudioTrack/VAudioTrack.vue"
import VAudioCollection from "~/components/VSearchResultsGrid/VAudioCollection.vue"
export default defineComponent({
name: "VRelatedAudio",
components: { VAudioTrack, LoadingIcon },
components: { VAudioCollection },
emits: {
interacted: defineEvent<[Omit<AudioInteractionData, "component">]>(),
},
setup() {
const uiStore = useUiStore()
const relatedMediaStore = useRelatedMediaStore()
const route = useRoute()
const audioTrackSize = computed(() => {
return uiStore.isBreakpoint("md") ? "l" : "s"
})
const media = computed(
() => (relatedMediaStore.media ?? []) as AudioDetail[]
)
Expand All @@ -86,25 +55,10 @@ export default defineComponent({
const fetchState = computed(() => relatedMediaStore.fetchState)
const { sendCustomEvent } = useAnalytics()
const sendSelectSearchResultEvent = (audio: AudioDetail) => {
sendCustomEvent("SELECT_SEARCH_RESULT", {
id: audio.id,
relatedTo: relatedMediaStore.mainMediaId,
mediaType: AUDIO,
provider: audio.provider,
query: useSearchStore().searchTerm,
})
}
return {
media,
showRelated,
fetchState,
audioTrackSize,
sendSelectSearchResultEvent,
}
},
})
Expand Down
21 changes: 19 additions & 2 deletions frontend/src/components/VAudioTrack/VAudioTrack.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
@keydown.native.shift.tab.exact="$emit('shift-tab', $event)"
@keydown="handleKeydown"
@blur="handleBlur"
@mousedown="$emit('mousedown', $event)"
@mousedown="handleMousedown"
@focus="$emit('focus', $event)"
>
<Component
Expand All @@ -21,6 +21,7 @@
>
<template #controller="waveformProps">
<VWaveform
ref="waveformRef"
v-bind="waveformProps"
:is-parent-seeking="isSeeking"
:peaks="audio.peaks"
Expand Down Expand Up @@ -82,6 +83,8 @@ import type { AudioDetail } from "~/types/media"
import { defineEvent } from "~/types/emits"
import type { AudioTrackClickEvent } from "~/types/events"
import VPlayPause from "~/components/VAudioTrack/VPlayPause.vue"
import VWaveform from "~/components/VAudioTrack/VWaveform.vue"
import VFullLayout from "~/components/VAudioTrack/layouts/VFullLayout.vue"
Expand Down Expand Up @@ -143,7 +146,7 @@ export default defineComponent({
emits: {
"shift-tab": defineEvent<[KeyboardEvent]>(),
interacted: defineEvent<[Omit<AudioInteractionData, "component">]>(),
mousedown: defineEvent<[MouseEvent]>(),
mousedown: defineEvent<[AudioTrackClickEvent]>(),
focus: defineEvent<[FocusEvent]>(),
},
setup(props, { emit }) {
Expand Down Expand Up @@ -474,6 +477,18 @@ export default defineComponent({
*/
const playPauseRef = ref<HTMLElement | null>(null)
/**
* A ref used on the waveform, so we can capture mousedown on the
* audio track outside it as it will open a detail page.
*/
const waveformRef = ref<{ $el: HTMLElement } | null>(null)
const handleMousedown = (event: MouseEvent) => {
const inWaveform =
waveformRef.value?.$el.contains(event.target as Node) ?? false
emit("mousedown", { event, inWaveform })
}
/**
* These layout-conditional props and listeners allow us
* to set properties on the parent element depending on
Expand Down Expand Up @@ -536,6 +551,7 @@ export default defineComponent({
handleSeeked,
handleKeydown,
handleBlur: seekable.listeners.blur,
handleMousedown,
isSeeking,
Expand All @@ -549,6 +565,7 @@ export default defineComponent({
containerAttributes,
playPauseRef,
waveformRef,
}
},
})
Expand Down
18 changes: 2 additions & 16 deletions frontend/src/components/VSearchResultsGrid/VAllResultsGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
:key="item.id"
:audio="item"
:search-term="searchTerm"
@interacted="handleInteraction"
@interacted="snackbar.hide"
@focus="snackbar.show"
/>
</template>
Expand All @@ -67,9 +67,7 @@ import { useSearchStore } from "~/stores/search"
import { useUiStore } from "~/stores/ui"
import { isDetail } from "~/types/media"
import type { AudioInteractionData } from "~/types/analytics"
import { useAnalytics } from "~/composables/use-analytics"
import { useAudioSnackbar } from "~/composables/use-audio-snackbar"
import { useI18n } from "~/composables/use-i18n"
Expand Down Expand Up @@ -97,8 +95,6 @@ export default defineComponent({
const mediaStore = useMediaStore()
const searchStore = useSearchStore()
const { sendCustomEvent } = useAnalytics()
const searchTerm = computed(() => searchStore.searchTerm)
const resultsLoading = computed(() => {
Expand Down Expand Up @@ -129,20 +125,11 @@ export default defineComponent({
() => fetchState.value.isFinished && allMedia.value.length === 0
)
const snackbar = useAudioSnackbar()
const uiStore = useUiStore()
const snackbar = useAudioSnackbar()
const isSidebarVisible = computed(() => uiStore.isFilterVisible)
const handleInteraction = (data: AudioInteractionData) => {
snackbar.hide()
sendCustomEvent("AUDIO_INTERACTION", {
...data,
component: "VAllResultsGrid",
})
}
return {
searchTerm,
isError,
Expand All @@ -157,7 +144,6 @@ export default defineComponent({
isSidebarVisible,
handleInteraction,
snackbar,
isDetail,
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/components/VSearchResultsGrid/VAudioCell.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
:search-term="searchTerm"
v-bind="$attrs"
v-on="$listeners"
@interacted="sendInteractionEvent"
@mousedown="sendSelectSearchResultEvent(audio)"
/>
</li>
Expand All @@ -19,6 +20,8 @@ import type { AudioDetail } from "~/types/media"
import { useAnalytics } from "~/composables/use-analytics"
import { AUDIO } from "~/constants/media"
import type { AudioInteractionData } from "~/types/analytics"
import VAudioTrack from "~/components/VAudioTrack/VAudioTrack.vue"
export default defineComponent({
Expand Down Expand Up @@ -46,9 +49,18 @@ export default defineComponent({
relatedTo: null,
})
}
const sendInteractionEvent = (
data: Omit<AudioInteractionData, "component">
) => {
sendCustomEvent("AUDIO_INTERACTION", {
...data,
component: "VAllResultsGrid",
})
}
return {
sendSelectSearchResultEvent,
sendInteractionEvent,
}
},
})
Expand Down
91 changes: 91 additions & 0 deletions frontend/src/components/VSearchResultsGrid/VAudioCollection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<template>
<section>
<VGridSkeleton
v-if="results.length === 0 && !fetchState.isFinished"
is-for-tab="audio"
/>
<VSnackbar size="large" :is-visible="snackbar.isVisible.value">
<i18n path="audioResults.snackbar.text" tag="p">
<template
v-for="keyboardKey in ['spacebar', 'left', 'right']"
#[keyboardKey]
>
<kbd :key="keyboardKey" class="font-sans">{{
$t(`audioResults.snackbar.${keyboardKey}`)
}}</kbd>
</template>
</i18n>
</VSnackbar>
<VAudioList
:collection-label="collectionLabel"
:is-related="isRelated"
:results="results"
@focus="snackbar.show"
@interacted="snackbar.hide"
@mousedown="snackbar.handleMouseDown"
/>
<footer v-if="!isRelated">
<VLoadMore />
</footer>
</section>
</template>

<script lang="ts">
import { defineComponent, PropType } from "vue"
import type { AudioDetail } from "~/types/media"
import type { FetchState } from "~/types/fetch-state"
import { useAudioSnackbar } from "~/composables/use-audio-snackbar"
import VAudioList from "~/components/VSearchResultsGrid/VAudioList.vue"
import VLoadMore from "~/components/VLoadMore.vue"
import VGridSkeleton from "~/components/VSkeleton/VGridSkeleton.vue"
import VSnackbar from "~/components/VSnackbar.vue"
import type { NuxtError } from "@nuxt/types"
/**
* This component shows a loading skeleton if the results are not yet loaded,
* and then shows the list of audio, with the Load more button if needed.
*/
export default defineComponent({
name: "VAudioCollection",
components: {
VSnackbar,
VAudioList,
VGridSkeleton,
VLoadMore,
},
props: {
results: {
type: Array as PropType<AudioDetail[]>,
default: () => [],
},
/**
* If used for Related audio, do not show the Load more button.
*/
isRelated: {
type: Boolean,
required: true,
},
fetchState: {
type: Object as PropType<FetchState<NuxtError> | FetchState>,
required: true,
},
/**
* The label used for the list of audio for accessibility.
*/
collectionLabel: {
type: String,
required: true,
},
},
setup() {
const snackbar = useAudioSnackbar()
return {
snackbar,
}
},
})
</script>
Loading

0 comments on commit 2b034f8

Please sign in to comment.