Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 77 additions & 6 deletions frontend/src/components/VImageResult/VImageResult.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { useI18n, useNuxtApp } from "#imports"
import { computed } from "vue"
import { computed, ref } from "vue"

import { IMAGE } from "#shared/constants/media"
import { singleResultQuery } from "#shared/utils/query-utils"
Expand Down Expand Up @@ -36,6 +36,10 @@
}
)

// NEW: Add reactive state for tracking image loading failures
const imageLoadFailed = ref(false)
const currentImageSrc = ref("")

const toAbsolutePath = (url: string, prefix = "https://") => {
if (url.startsWith("http://") || url.startsWith("https://")) {
return url
Expand Down Expand Up @@ -73,8 +77,33 @@
*/
const onImageLoadError = (event: Event) => {
const element = event.target as HTMLImageElement
element.src = element.src === props.image.url ? errorImage : props.image.url
const newSrc = element.src === props.image.url ? errorImage : props.image.url
element.src = newSrc
currentImageSrc.value = newSrc

// NEW: Track if we've fallen back to the error image
if (newSrc === errorImage) {
imageLoadFailed.value = true
}
}

/**
* NEW: Handle successful image load
* @param event - the load event.
*/
const onImageLoad = (event: Event) => {
const element = event.target as HTMLImageElement
currentImageSrc.value = element.src

// Reset failure state if image loads successfully and it's not the error placeholder
if (element.src !== errorImage) {
imageLoadFailed.value = false
}

// Call existing dimension handling
getImgDimension(event)
}

/**
* If the image is not square, on the image load event, update
* the img's height and width with image natural dimensions.
Expand All @@ -92,6 +121,20 @@
const imageTitle = t("browsePage.aria.imageTitle", {
title: props.image.title,
})

// NEW: Computed property for accessible alt text that updates when image fails
const accessibleAltText = computed(() => {
if (shouldBlur.value) {
return `${t('sensitiveContent.title.image')}`
}

if (imageLoadFailed.value) {
return `${t('browsePage.aria.imageNotAvailable', { title: props.image.title }, 'Image not available: {title}')}`

Check warning on line 132 in frontend/src/components/VImageResult/VImageResult.vue

View workflow job for this annotation

GitHub Actions / Lint files

'browsePage' does not exist in localization message resources
}

return props.image.title
})

const contextSensitiveLabel = computed(() =>
shouldBlur.value ? t("sensitiveContent.title.image") : imageTitle
)
Expand Down Expand Up @@ -126,6 +169,12 @@
}

const { isHidden: shouldBlur } = useSensitiveMedia(props.image)

// NEW: Watch for image URL changes to reset failure state
watch(() => imageUrl.value, () => {
imageLoadFailed.value = false
currentImageSrc.value = ""
})
</script>

<template>
Expand Down Expand Up @@ -164,19 +213,26 @@
:class="[
isSquare ? 'h-full' : 'margin-auto sm:aspect-[--img-aspect-ratio]',
]"
:alt="
shouldBlur ? `${$t('sensitiveContent.title.image')}` : image.title
"
:alt="accessibleAltText"
:src="imageUrl"
:width="imgWidth"
:height="imgHeight"
itemprop="thumbnailUrl"
@load="getImgDimension"
@load="onImageLoad"
@error="onImageLoadError($event)"
/>
<!-- NEW: Screen reader announcement for image load failures -->
<span
v-if="imageLoadFailed"
class="sr-only"
aria-live="polite"
role="status"
>
{{ $t('browsePage.aria.imageLoadFailed', { title: props.image.title }, 'This image is currently unavailable. The original title was: {title}') }}
</span>
<span
class="col-span-full row-span-full flex items-center justify-center bg-blur text-default backdrop-blur-xl duration-200 motion-safe:transition-opacity"
:class="shouldBlur ? 'opacity-100' : 'opacity-0'"

Check warning on line 235 in frontend/src/components/VImageResult/VImageResult.vue

View workflow job for this annotation

GitHub Actions / Lint files

'browsePage' does not exist in localization message resources
data-testid="blur-overlay"
aria-hidden="true"
>
Expand Down Expand Up @@ -210,3 +266,18 @@
</VLink>
</li>
</template>

<style scoped>
/* NEW: Screen reader only utility class */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>
Loading