frontend/src/views/DetailsView/DetailsViewImages.vue
nquidox bb40d17e6b
All checks were successful
/ Make image (push) Successful in 39s
details view image component refactor
2025-10-19 17:30:22 +03:00

230 lines
5.1 KiB
Vue

<script setup>
import { onMounted, ref } from 'vue'
import { NModal, NUpload, useMessage } from 'naive-ui'
import BoxIcon from '@/components/icons/BoxIcon.vue'
import { useMerchImagesApi } from '@/api/merchImages.js'
const { uploadImage, getImageUrl, deleteImage } = useMerchImagesApi()
const message = useMessage()
const props = defineProps({
merchUuid: { type: String, required: true },
})
const showModal = ref(false)
const previewImageUrl = ref('')
const fileList = ref([])
function handlePreview(file) {
if (file.file) {
previewImageUrl.value = URL.createObjectURL(file.file)
} else if (file.url) {
previewImageUrl.value = file.url
}
showModal.value = true
}
function onModalClose() {
if (previewImageUrl.value && previewImageUrl.value.startsWith('blob:')) {
URL.revokeObjectURL(previewImageUrl.value)
}
previewImageUrl.value = ''
}
const fileInput = ref(null)
function triggerFileInput() {
fileInput.value?.click()
}
function onFileInputChange(event) {
const files = event.target.files
if (!files || files.length === 0) return
const file = files[0]
handleUpload({
fileList: [
{
file,
name: file.name,
status: 'pending',
},
],
})
event.target.value = ''
}
async function handleUpload({ fileList: newFileList }) {
const file = newFileList[newFileList.length - 1]
try {
await uploadImage(props.merchUuid, file.file)
const { imgUrl } = await getImageUrl(props.merchUuid, 'full')
message.success('Image uploaded successfully.')
fileList.value = [
{
name: file.name,
url: imgUrl,
status: 'finished',
},
]
} catch (error) {
message.error('Upload error: ' + (error.message || 'Unknown error.'))
}
}
onMounted(async () => {
try {
const { imgUrl } = await getImageUrl(props.merchUuid, 'full')
fileList.value = [
{
name: 'full.jpg',
url: imgUrl,
status: 'finished',
},
]
} catch (error) {
fileList.value = []
if (!error.message?.includes('404')) {
console.error('Error getting image: ', error)
}
}
})
const showConfirmDelete = ref(false)
const deleteImageHandler = async () => {
showConfirmDelete.value = true
return false //prevents instant "delete image" in n-upload before confirm
}
const confirmDeleteImage = async () => {
try {
await deleteImage(props.merchUuid)
fileList.value = []
message.success('Image deleted successfully.')
} catch (error) {
message.error('Image delete error: ' + (error.message || 'Unknown error.'))
} finally {
showConfirmDelete.value = false
}
}
const cancelDelete = () => {
showConfirmDelete.value = false
}
</script>
<template>
<div>
<div v-if="fileList.length === 0" class="upload-wrapper" @click="triggerFileInput">
<BoxIcon class="upload-icon" />
<span class="upload-text">Click to Upload</span>
</div>
<div v-else class="preview-container clickable-image">
<div>
<img
:src="fileList[0].url"
alt="Preview"
class="preview-image"
@click="triggerFileInput"
/>
</div>
<div class="button-container-evenly button-gap">
<n-button type="primary" @click="handlePreview(fileList[0])">Preview</n-button>
<n-button type="error" @click="deleteImageHandler">Delete</n-button>
</div>
</div>
</div>
<n-modal v-model:show="showModal" preset="card" title="Preview" @after-leave="onModalClose" class="image-preview-modal">
<img :src="previewImageUrl" alt="Preview" class="preview-modal-image"/>
</n-modal>
<n-modal v-model:show="showConfirmDelete" preset="dialog" title="Confirmation">
<template #default>
<p>Confirm delete image</p>
</template>
<template #action>
<n-button @click="confirmDeleteImage" type="error">Delete</n-button>
<n-button @click="cancelDelete">Cancel</n-button>
</template>
</n-modal>
<input
ref="fileInput"
type="file"
accept="image/*"
style="display: none"
@change="onFileInputChange"
/>
</template>
<style scoped>
.upload-wrapper {
width: 300px;
max-width: 300px;
height: 300px;
max-height: 300px;
border: 1px dashed #ccc;
border-radius: 8px;
background-color: #fafafa;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition:
background-color 0.2s ease,
border-color 0.2s ease;
}
.upload-wrapper:hover {
border-color: #18a058;
background-color: #f0fdf4;
}
.upload-icon {
width: 150px;
max-width: 150px;
height: 150px;
max-height: 150px;
opacity: 0.6;
}
.upload-text {
margin-top: 8px;
font-size: 14px;
color: #555;
background-color: rgba(24, 160, 88, 0.4);
padding: 6px 12px;
border-radius: 6px;
display: inline-block;
}
.preview-container {
display: block;
width: 100%;
max-width: 400px;
margin: 0 auto;
}
.preview-image {
width: 100%;
height: auto;
display: block;
max-width: 100%;
object-fit: contain;
}
.clickable-image {
cursor: pointer;
transition: opacity 0.2s;
}
.clickable-image:hover {
opacity: 0.9;
}
</style>