The CWA is in heavy development
The CWA is still in alpha and not ready for production - some code and implementations are likely to change. If you would like to try out the CWA, please enjoy what we have provided and feel free to provide feedback, or get involved on GitHub.
DraftComponent Helpers

Images & Media

Display uploaded files with useCwaImageResource, manage uploads in admin tabs with useCwaResourceUpload, and use Imagine filter variants.

When a PHP component uses #[Silverback\Uploadable], use useCwaImageResource instead of useCwaResource. 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 { toRef } from 'vue'
import type { IriProp } from '#cwa/composables/cwa-resource'
import { useCwaImageResource } from '#imports'

const props = defineProps<IriProp>()

const {
    getResource,
    exposeMeta,
    contentUrl,
    displayMedia,
    handleLoad
} = useCwaImageResource(toRef(props, 'iri'))

const resource = getResource()
defineExpose(exposeMeta)
</script>

Return Values

ReturnTypeDescription
getResource() => Ref<Resource>Same as useCwaResource
exposeMetaobjectPass to defineExpose
contentUrlComputedRef<string | undefined>Public URL of the uploaded file
displayMediaComputedRef<boolean>true when contentUrl is set AND the image has loaded
loadedRef<boolean>Becomes true when handleLoad() is called
handleLoad() => voidCall on the <img> element's @load event

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 } = useCwaImageResource(
    toRef(props, 'iri'),
    { imagineFilterName: 'thumbnail' }
)

Without imagineFilterName, contentUrl is the raw uploaded file URL.

Accessing All Imagine Variants

The full map of variants is nested inside _metadata.mediaObjects:

const resource = getResource()

// All variants for the 'file' upload property
const mediaObjects = computed(() => resource.value?.data?._metadata?.mediaObjects?.file)

const thumbnailUrl = mediaObjects.value?.thumbnail?.contentUrl
const heroUrl = mediaObjects.value?.hero?.contentUrl
const originalUrl = mediaObjects.value?.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">
const { getResource, exposeMeta, contentUrl } = useCwaImageResource(toRef(props, 'iri'))
const resource = getResource()
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.