-
Konrad Mohrfeldt authored
Using the same name as the native HTML dialog is a bad idea.
Konrad Mohrfeldt authoredUsing the same name as the native HTML dialog is a bad idea.
ImagePickerDialog.vue 4.68 KiB
<template>
<ADialog v-model="localIsOpen" is-modal class="tw-w-screen lg:tw-w-[60vw] tw-max-w-[800px]">
<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>
</ADialog>
</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 ADialog from '../generic/ADialog.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>