<template> <Dialog v-model="localIsOpen" class="tw-w-screen lg:tw-w-[60vw] tw-max-w-[800px]" is-modal> <template #header> <p class="tw-text-lg tw-font-semibold tw-m-0">{{ t('imagePicker.title') }}</p> </template> <div v-if="!selectedImage && !showBrowser" class="tw-flex tw-flex-wrap tw-items-center tw-gap-2" > <ImageUploader class="tw-grow md:tw-grow-0" @input="setImage" /> <button type="button" class="btn btn-sm btn-secondary tw-flex tw-items-center tw-grow md:tw-grow-0" @click.stop="showBrowser = true" > <icon-carbon-search class="tw-mr-1 tw-w-6 tw-h-6" /> {{ t('imagePicker.browseImages') }} </button> <button v-if="currentImage" type="button" class="btn btn-sm btn-secondary tw-flex tw-items-center tw-grow md:tw-grow-0" @click.stop="selectedImage = currentImage" > <icon-carbon-pen class="tw-mr-1 tw-w-6 tw-h-6" /> {{ t('imagePicker.editCurrentImage') }} </button> </div> <p v-if="error" role="alert" aria-live="assertive" class="alert alert-danger"> {{ error.message }} </p> <ImageBrowser v-if="showBrowser" @input="setImage" /> <ImageEditor v-if="selectedImage" v-model="selectedImage" /> <template v-if="showBrowser || selectedImage" #footer> <div class="tw-flex tw-gap-x-6"> <button v-if="selectedImage" :disabled="isSaving" type="button" class="btn btn-primary" @click.stop="selectedImage && saveAndSelectImage(selectedImage)" > <template v-if="isSelectedImageNew">{{ t('imagePicker.useImage') }}</template> <template v-else>{{ t('imagePicker.saveChanges') }}</template> </button> <button v-if="selectedImage" type="button" class="btn" @click.stop="deselectImage"> {{ t('imagePicker.abort') }} </button> <button v-if="showBrowser" type="button" class="btn btn-secondary" @click.stop="showBrowser = false" > {{ t('imagePicker.abort') }} </button> </div> </template> </Dialog> </template> <script lang="ts" setup> import { computed, ref } from 'vue' import { useI18n } from '@/i18n' import { Image, NewImage, useImage, useImageStore } from '@/stores/images' import { useAsyncFunction, useUpdatableState } from '@/util' import Dialog from '../generic/Dialog.vue' import ImageBrowser from './ImageBrowser.vue' import ImageEditor from './ImageEditor.vue' import ImageUploader from './ImageUploader.vue' import { APIResponseError } from '@/api' const props = defineProps<{ isOpen: boolean modelValue: number | null }>() const emit = defineEmits<{ (e: 'show', state: boolean): void (e: 'update:modelValue', value: number | null): void }>() const { t } = useI18n() const imageStore = useImageStore() const localIsOpen = useUpdatableState( computed(() => props.isOpen), (isOpen) => emit('show', isOpen), ) const error = ref<Error>() const showBrowser = ref(false) const selectedImage = ref<Image | NewImage | null>(null) const currentImage = useImage(computed(() => props.modelValue)) const isSelectedImageNew = computed(() => { if (!selectedImage.value) return false else if ('id' in selectedImage.value) return selectedImage.value.id !== props.modelValue else return true }) const { isLoading: isSaving, fn: saveAndSelectImage } = useAsyncFunction( async (image: Image | NewImage) => { error.value = undefined const data = new FormData() data.set('ppoi', image.ppoi) data.set('alt_text', image.alt_text ?? '') data.set('credits', image.credits ?? '') let newOrUpdatedImage: Image try { if ('id' in image) { newOrUpdatedImage = await imageStore.update(image.id, data) } else { data.set('image', image.file, image.file.name) newOrUpdatedImage = await imageStore.create(data) } } catch (_error) { const _errorObj = _error instanceof Error ? _error : new Error(String(_error)) const message = _errorObj instanceof APIResponseError && _errorObj.response.status === 413 ? t('imagePicker.error.tooLarge') : t('imagePicker.error.default') // eslint-disable-next-line no-undef error.value = new AggregateError([_errorObj], message) return } emit('update:modelValue', newOrUpdatedImage.id) emit('show', false) }, ) function setImage(image: Image | NewImage | null) { selectedImage.value = image showBrowser.value = false } function deselectImage() { selectedImage.value = null error.value = undefined } </script> <script lang="ts"> export default { compatConfig: { MODE: 3, }, } </script>