Images & Media
When a PHP component uses #[Silverback\Uploadable], use useCwaComponent with the withImage() plugin. It adds computed values for the uploaded file URL, load state, and Imagine filter variants.
Display Component
<!-- app/cwa/components/Image/Image.vue -->
<template>
<div class="relative">
<Transition name="fade">
<NuxtImg
v-if="displayMedia"
:src="contentUrl"
class="w-full h-full object-cover"
@load="handleLoad"
/>
</Transition>
<div
v-if="!displayMedia"
class="w-full h-64 bg-gray-200 animate-pulse rounded"
/>
</div>
</template>
<script setup lang="ts">
import type { IriProp } from '#cwa/composables/cwa-resource'
import { useCwaComponent, withImage } from '#imports'
const props = defineProps<IriProp>()
const {
resource,
exposeMeta,
contentUrl,
displayMedia,
handleLoad
} = useCwaComponent(props, [withImage()])
defineExpose(exposeMeta)
</script>
Return Values from withImage()
| Return | Type | Description |
|---|---|---|
contentUrl | ComputedRef<string | undefined> | Public URL of the uploaded file |
displayMedia | ComputedRef<boolean> | true when contentUrl is set AND the image has loaded |
loaded | Ref<boolean> | Becomes true when handleLoad() is called |
handleLoad | () => void | Call on the <img> element's @load event |
mediaObjects | ComputedRef | Full _metadata.mediaObjects map for all Imagine variants |
displayMedia prevents showing a broken or partially loaded image. The skeleton shows until both contentUrl exists and the browser fires the load event.
Using a Specific Imagine Filter
Pass imagineFilterName to use a specific image variant as contentUrl:
const { contentUrl, displayMedia, handleLoad } = useCwaComponent(props, [
withImage({ imagineFilterName: 'thumbnail' })
])
Without imagineFilterName, contentUrl is the raw uploaded file URL.
Accessing All Imagine Variants
The full map of variants is available via mediaObjects:
const { resource, mediaObjects } = useCwaComponent(props, [withImage()])
// All variants for the 'file' upload property
const thumbnailUrl = computed(() => mediaObjects.value?.file?.thumbnail?.contentUrl)
const heroUrl = computed(() => mediaObjects.value?.file?.hero?.contentUrl)
const originalUrl = computed(() => mediaObjects.value?.file?.contentUrl)
The key under mediaObjects is the PHP property name from #[UploadableField]. Variant keys match the Imagine filter names configured on the PHP entity.
Admin Upload Tab
<!-- app/cwa/components/Image/admin/Image.vue -->
<template>
<div class="p-4">
<CwaUiFormFile
v-model="filenameInputModel"
label="Upload Image"
:disabled="updating"
:file-exists="fileExists"
@change="handleInputChangeFile"
@delete="handleInputDeleteFile"
/>
</div>
</template>
<script setup lang="ts">
import { toRef } from 'vue'
import type { IriProp } from '#cwa/composables/cwa-resource'
import { useCwaResourceManagerTab, useCwaResourceUpload } from '#imports'
const props = defineProps<IriProp>()
const { exposeMeta, iri } = useCwaResourceManagerTab({ name: 'Image', order: 1 })
const {
filenameInputModel,
updating,
fileExists,
handleInputChangeFile,
handleInputDeleteFile
} = useCwaResourceUpload(iri, 'file') // 'file' = the PHP property name
defineExpose(exposeMeta)
</script>
useCwaResourceUpload(iri, propertyName) handles file selection, multipart upload to {iri}/upload, and deletion via PATCH.
Video, PDF, and Other Files
The same pattern works for any file type. For non-image files, displayMedia is true as soon as contentUrl is available (no image load event to wait for):
<template>
<a v-if="contentUrl" :href="contentUrl" download>
Download PDF
</a>
</template>
<script setup lang="ts">
import type { IriProp } from '#cwa/composables/cwa-resource'
import { useCwaComponent, withImage } from '#imports'
const props = defineProps<IriProp>()
const { exposeMeta, contentUrl } = useCwaComponent(props, [withImage()])
defineExpose(exposeMeta)
</script>
For video:
<video v-if="contentUrl" :src="contentUrl" controls class="w-full" />
Transition While Loading
<style>
.fade-enter-active, .fade-leave-active { transition: opacity 0.3s; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
</style>
The <Transition name="fade"> wrapping the <NuxtImg> produces a smooth fade-in once the image loads.