Skip to content
Snippets Groups Projects
ImagePickerDialog.vue 4.68 KiB
Newer Older
  • Learn to ignore specific revisions
  • <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>