Skip to content

Commit

Permalink
Fix audio snackbar and box audio seeking (#2743)
Browse files Browse the repository at this point in the history
* Move analytics to `VAudioResult` component

Fix Box layout seeking; select single result event.

* Make single-result VR tests less flaky

* Make box non-seekable

* Show snackbar for all audio tracks
  • Loading branch information
obulat authored Aug 12, 2023
1 parent f323231 commit a80aad0
Show file tree
Hide file tree
Showing 26 changed files with 167 additions and 122 deletions.
61 changes: 46 additions & 15 deletions frontend/src/components/VAudioTrack/VAudioTrack.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
@keydown="handleKeydown"
@blur="handleBlur"
@mousedown="handleMousedown"
@focus="$emit('focus', $event)"
@focus="handleFocus"
>
<Component
:is="layoutComponent"
Expand All @@ -31,6 +31,8 @@
:message="message"
@seeked="handleSeeked"
@toggle-playback="handleToggle"
@blur="handleWaveformBlur"
@focus="handleWaveformFocus"
/>
</template>

Expand Down Expand Up @@ -61,6 +63,7 @@ import { useActiveAudio } from "~/composables/use-active-audio"
import { defaultRef } from "~/composables/default-ref"
import { useI18n } from "~/composables/use-i18n"
import { useSeekable } from "~/composables/use-seekable"
import { useAudioSnackbar } from "~/composables/use-audio-snackbar"
import {
useMatchSearchRoutes,
useMatchSingleResultRoutes,
Expand Down Expand Up @@ -427,13 +430,17 @@ export default defineComponent({
event = "pause"
break
}
if (event) {
emit("interacted", {
event,
id: props.audio.id,
provider: props.audio.provider,
})
}
emitInteracted(event)
}
const emitInteracted = (event?: AudioInteraction) => {
if (!event) return
snackbar.dismiss()
emit("interacted", {
event,
id: props.audio.id,
provider: props.audio.provider,
})
}
/* Interface with VWaveform */
Expand All @@ -450,11 +457,7 @@ export default defineComponent({
if (localAudio) {
localAudio.currentTime = frac * duration.value
}
emit("interacted", {
event: "seek",
id: props.audio.id,
provider: props.audio.provider,
})
emitInteracted("seek")
}
/* Layout */
Expand All @@ -475,7 +478,7 @@ export default defineComponent({
* so we can capture clicks and skip
* sending an event to the boxed layout.
*/
const playPauseRef = ref<HTMLElement | null>(null)
const playPauseRef = ref<{ $el: HTMLElement } | null>(null)
/**
* A ref used on the waveform, so we can capture mousedown on the
Expand All @@ -486,6 +489,7 @@ export default defineComponent({
const handleMousedown = (event: MouseEvent) => {
const inWaveform =
waveformRef.value?.$el.contains(event.target as Node) ?? false
snackbar.handleMouseDown()
emit("mousedown", { event, inWaveform })
}
Expand Down Expand Up @@ -530,6 +534,7 @@ export default defineComponent({
duration,
currentTime,
isReady: ref(true),
isSeekable: computed(() => props.layout !== "box"),
onSeek: handleSeeked,
onTogglePlayback: togglePlayback,
})
Expand All @@ -543,15 +548,41 @@ export default defineComponent({
...layoutBasedProps.value,
}))
const snackbar = useAudioSnackbar()
const handleFocus = (event: FocusEvent) => {
snackbar.show()
emit("focus", event)
}
const handleBlur = () => {
snackbar.hide()
seekable.listeners.blur()
}
const handleWaveformFocus = (event: FocusEvent) => {
if (!isComposite.value) {
handleFocus(event)
}
}
const handleWaveformBlur = () => {
if (!isComposite.value) {
handleBlur()
}
}
return {
status,
message,
ariaLabel,
handleToggle,
handleSeeked,
handleKeydown,
handleBlur: seekable.listeners.blur,
handleBlur,
handleMousedown,
handleFocus,
handleWaveformBlur,
handleWaveformFocus,
isSeeking,
Expand Down
14 changes: 13 additions & 1 deletion frontend/src/components/VAudioTrack/VWaveform.vue
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ export default defineComponent({
*/
seeked: defineEvent<[number]>(),
"toggle-playback": defineEvent<[]>(),
focus: defineEvent<[FocusEvent]>(),
blur: defineEvent<[FocusEvent]>(),
},
setup(props, { emit }) {
/* Utils */
Expand Down Expand Up @@ -406,6 +408,7 @@ export default defineComponent({
const { isSeeking: isSelfSeeking, ...seekable } = useSeekable({
duration: toRef(props, "duration"),
currentTime: toRef(props, "currentTime"),
isSeekable,
isReady,
onSeek: (frac) => {
clearSeekProgress()
Expand Down Expand Up @@ -467,6 +470,13 @@ export default defineComponent({
event.stopPropagation()
event.preventDefault()
}
const handleFocus = (event: FocusEvent) => {
emit("focus", event)
}
const handleBlur = (event: FocusEvent) => {
seekable.listeners.blur()
emit("blur", event)
}
/* v-on */
Expand All @@ -478,7 +488,9 @@ export default defineComponent({
mouseup: handleMouseUp,
mouseleave: handleMouseLeave,
click: handleClick,
...seekable.listeners,
blur: handleBlur,
focus: handleFocus,
keydown: seekable.listeners.keydown,
}
} else {
return {}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/VAudioTrack/layouts/VBoxLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import { computed, defineComponent, PropType } from "vue"
import type { AudioDetail } from "~/types/media"
import type { AudioSize } from "~/constants/audio"
import { useI18n } from "~/composables/use-i18n"
import { useSensitiveMedia } from "~/composables/use-sensitive-media"
import VLicense from "~/components/VLicense/VLicense.vue"
Expand All @@ -66,6 +65,7 @@ export default defineComponent({
},
setup(props) {
const i18n = useI18n()
const isSmall = computed(() => props.size === "s")
const width = computed(() => {
Expand Down
7 changes: 3 additions & 4 deletions frontend/src/components/VAudioTrack/layouts/VRowLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
/>
<slot
name="controller"
:features="features"
:features="audioFeatures"
:feature-notices="featureNotices"
:is-tabbable="false"
/>
Expand All @@ -107,7 +107,7 @@ import { computed, defineComponent, PropType } from "vue"
import { timeFmt } from "~/utils/time-fmt"
import type { AudioDetail } from "~/types/media"
import type { AudioSize } from "~/constants/audio"
import { audioFeatures, AudioSize } from "~/constants/audio"
import { useSensitiveMedia } from "~/composables/use-sensitive-media"
Expand Down Expand Up @@ -136,7 +136,6 @@ export default defineComponent({
duration?: string
seek?: string
} = {}
const features = ["timestamps", "duration", "seek"]
const isSmall = computed(() => props.size === "s")
const isMedium = computed(() => props.size === "m")
Expand All @@ -147,7 +146,7 @@ export default defineComponent({
return {
timeFmt,
features,
audioFeatures,
featureNotices,
isSmall,
Expand Down
24 changes: 12 additions & 12 deletions frontend/src/components/VSearchResultsGrid/VAllResultsGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
:to="contentLinkPath(mediaType)"
/>
</div>
<VSnackbar size="large" :is-visible="snackbar.isVisible.value">
<VSnackbar size="large" :is-visible="isSnackbarVisible">
<i18n path="allResults.snackbar.text" tag="p">
<template #spacebar>
<kbd class="font-sans">{{ $t(`allResults.snackbar.spacebar`) }}</kbd>
Expand Down Expand Up @@ -44,13 +44,13 @@
:search-term="searchTerm"
aspect-ratio="square"
/>
<VAudioCell
<VAudioResult
v-if="isDetail.audio(item)"
:key="item.id"
:audio="item"
:search-term="searchTerm"
@interacted="snackbar.hide"
@focus="snackbar.show"
layout="box"
:is-related="false"
/>
</template>
</ol>
Expand All @@ -61,21 +61,21 @@

<script lang="ts">
import { computed, defineComponent } from "vue"
import { storeToRefs } from "pinia"
import { useMediaStore } from "~/stores/media"
import { useSearchStore } from "~/stores/search"
import { useUiStore } from "~/stores/ui"
import { isDetail } from "~/types/media"
import { useAudioSnackbar } from "~/composables/use-audio-snackbar"
import { useI18n } from "~/composables/use-i18n"
import type { SupportedMediaType } from "~/constants/media"
import VSnackbar from "~/components/VSnackbar.vue"
import VImageCell from "~/components/VImageCell/VImageCell.vue"
import VAudioCell from "~/components/VSearchResultsGrid/VAudioCell.vue"
import VAudioResult from "~/components/VSearchResultsGrid/VAudioResult.vue"
import VLoadMore from "~/components/VLoadMore.vue"
import VContentLink from "~/components/VContentLink/VContentLink.vue"
import VGridSkeleton from "~/components/VSkeleton/VGridSkeleton.vue"
Expand All @@ -85,7 +85,7 @@ export default defineComponent({
components: {
VSnackbar,
VImageCell,
VAudioCell,
VAudioResult,
VLoadMore,
VGridSkeleton,
VContentLink,
Expand Down Expand Up @@ -126,9 +126,10 @@ export default defineComponent({
)
const uiStore = useUiStore()
const snackbar = useAudioSnackbar()
const isSidebarVisible = computed(() => uiStore.isFilterVisible)
const {
areInstructionsVisible: isSnackbarVisible,
isFilterVisible: isSidebarVisible,
} = storeToRefs(uiStore)
return {
searchTerm,
Expand All @@ -143,8 +144,7 @@ export default defineComponent({
contentLinkPath,
isSidebarVisible,
snackbar,
isSnackbarVisible,
isDetail,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
v-if="results.length === 0 && !fetchState.isFinished"
is-for-tab="audio"
/>
<VSnackbar size="large" :is-visible="snackbar.isVisible.value">
<VSnackbar size="large" :is-visible="isSnackbarVisible">
<i18n path="audioResults.snackbar.text" tag="p">
<template
v-for="keyboardKey in ['spacebar', 'left', 'right']"
Expand All @@ -20,9 +20,6 @@
:collection-label="collectionLabel"
:is-related="isRelated"
:results="results"
@focus="snackbar.show"
@interacted="snackbar.hide"
@mousedown="snackbar.handleMouseDown"
/>
<footer v-if="!isRelated">
<VLoadMore />
Expand Down Expand Up @@ -81,10 +78,10 @@ export default defineComponent({
},
},
setup() {
const snackbar = useAudioSnackbar()
const { isVisible: isSnackbarVisible } = useAudioSnackbar()
return {
snackbar,
isSnackbarVisible,
}
},
})
Expand Down
Loading

0 comments on commit a80aad0

Please sign in to comment.