Initial commit: PC Builder AdonisJS project
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<div class="relative w-full" :id="props.galleryId">
|
||||
<div v-if="editMode" class="grid grid-cols-3 gap-4 w-full min-h-10">
|
||||
<div
|
||||
v-for="image in combinedImages"
|
||||
:key="getKeyForImage(image)"
|
||||
class="relative cursor-move group"
|
||||
draggable="true"
|
||||
@dragstart="(event) => dragStart(event, combinedImages.indexOf(image))"
|
||||
@dragover.prevent
|
||||
@dragenter.stop.prevent="handleDragEnter(combinedImages.indexOf(image))"
|
||||
@drop.stop.prevent="drop"
|
||||
>
|
||||
<img :src="getImageSrc(image)" class="w-full h-66 object-cover rounded-lg" />
|
||||
<button
|
||||
v-if="editMode"
|
||||
class="absolute top-2 right-2 bg-white text-gray-800 w-6 h-6 flex items-center justify-center p-0 rounded-full hover:bg-gray-300 transition-opacity opacity-0 group-hover:opacity-100"
|
||||
@click.stop.prevent="removeImage(image)"
|
||||
>
|
||||
<img :src="crossIcon" alt="Delete" class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<Carousel v-bind="adjustedSettings" v-if="combinedImages.length != 0" ref="carousel">
|
||||
<Slide v-for="image in combinedImages" :key="getKeyForImage(image)">
|
||||
<div class="carousel__item">
|
||||
<img :src="getImageSrc(image)" alt="" class="carousel-image" />
|
||||
</div>
|
||||
</Slide>
|
||||
<template #addons>
|
||||
<Navigation />
|
||||
|
||||
<Pagination />
|
||||
</template>
|
||||
</Carousel>
|
||||
</div>
|
||||
<button
|
||||
v-if="editMode"
|
||||
@click.stop.prevent="showModal = true"
|
||||
class="py-2 px-4 bg-indigo-600 text-white font-semibold rounded hover:bg-blue-700 absolute bottom-0 right-0 z-50"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<div
|
||||
v-if="showModal"
|
||||
class="fixed inset-0 bg-black/50 flex justify-center items-center z-50"
|
||||
>
|
||||
<div class="bg-white p-6 rounded shadow-lg relative w-1/4">
|
||||
<button
|
||||
@click.prevent="resetModal"
|
||||
class="absolute top-2 right-2 text-gray-600 hover:text-gray-900"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<h3 class="text-lg font-semibold mb-4">Add a New Image</h3>
|
||||
<div class="flex items-center">
|
||||
<input type="file" ref="fileInput" @change="handleFileUpload" class="hidden" />
|
||||
<button
|
||||
@click.prevent="triggerFileInput"
|
||||
class="bg-blue-500 text-white rounded hover:bg-blue-700 h-10 px-4"
|
||||
>
|
||||
Browse
|
||||
</button>
|
||||
<div
|
||||
v-if="fileName"
|
||||
class="ml-4 p-2 border border-gray-300 rounded flex items-center h-10 w-full overflow-hidden whitespace-nowrap text-ellipsis"
|
||||
>
|
||||
{{ formatFileName(fileName) }}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@click.prevent="addImage"
|
||||
class="w-full mt-4 bg-blue-500 text-white p-2 rounded hover:bg-blue-700"
|
||||
>
|
||||
Add Image
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { crossIcon } from '~/assets/icons'
|
||||
import { Navigation, Pagination } from 'vue3-carousel'
|
||||
import { NewImage, ServerImage } from '../../../app/types/UItypes'
|
||||
import { Image } from '~/pages/types/UITypes'
|
||||
|
||||
const props = defineProps<{
|
||||
galleryId: string
|
||||
serverImages: ServerImage[]
|
||||
editMode?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const fileInput = ref<null | HTMLInputElement>(null)
|
||||
const serverImages = ref<ServerImage[]>([...props.serverImages])
|
||||
const newImages = ref<NewImage[]>([])
|
||||
const showModal = ref(false)
|
||||
const newImageFile = ref<File | null>(null)
|
||||
const dragIndex = ref<number | null>(null)
|
||||
const fileName = ref('')
|
||||
const editMode = props.editMode ?? false
|
||||
|
||||
const combinedImages = computed(() => [...serverImages.value, ...newImages.value])
|
||||
|
||||
watch(
|
||||
[serverImages, newImages],
|
||||
() => {
|
||||
reassignOrder()
|
||||
emit('update:modelValue', combinedImages.value)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
function handleFileUpload(event: Event) {
|
||||
const file = (event.target as HTMLInputElement).files?.[0]
|
||||
if (file) {
|
||||
fileName.value = file.name
|
||||
newImageFile.value = file
|
||||
}
|
||||
}
|
||||
|
||||
function addImage() {
|
||||
if (newImageFile.value) {
|
||||
newImages.value.push({
|
||||
file: newImageFile.value,
|
||||
order: combinedImages.value.length + 1,
|
||||
})
|
||||
resetModal()
|
||||
}
|
||||
}
|
||||
|
||||
function removeImage(image: Image) {
|
||||
if ('id' in image) {
|
||||
const index = serverImages.value.findIndex((img) => img.id === image.id)
|
||||
if (index !== -1) {
|
||||
serverImages.value.splice(index, 1)
|
||||
}
|
||||
} else {
|
||||
const index = newImages.value.findIndex((img) => img.file === image.file)
|
||||
if (index !== -1) {
|
||||
newImages.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetModal() {
|
||||
newImageFile.value = null
|
||||
fileName.value = ''
|
||||
showModal.value = false
|
||||
}
|
||||
|
||||
function triggerFileInput() {
|
||||
fileInput.value?.click()
|
||||
}
|
||||
|
||||
function dragStart(event: DragEvent, index: number) {
|
||||
dragIndex.value = index
|
||||
event.dataTransfer!.effectAllowed = 'move'
|
||||
}
|
||||
|
||||
function handleDragEnter(index: number) {
|
||||
if (dragIndex.value !== null && index !== dragIndex.value) {
|
||||
const sourceArray =
|
||||
dragIndex.value < serverImages.value.length ? serverImages.value : newImages.value
|
||||
const draggedItem = sourceArray.splice(dragIndex.value, 1)[0]
|
||||
sourceArray.splice(index, 0, draggedItem as any) // Utilisation de 'as any' pour r├®soudre le probl├¿me de typage
|
||||
dragIndex.value = index
|
||||
}
|
||||
}
|
||||
|
||||
function drop() {
|
||||
dragIndex.value = null
|
||||
reassignOrder()
|
||||
}
|
||||
|
||||
function reassignOrder() {
|
||||
combinedImages.value.forEach((image, index) => {
|
||||
image.order = index + 1
|
||||
})
|
||||
}
|
||||
|
||||
function getImageSrc(image: Image): string {
|
||||
return 'link' in image ? image.link : URL.createObjectURL(image.file)
|
||||
}
|
||||
|
||||
function getKeyForImage(image: Image): string {
|
||||
return 'id' in image ? `server-${image.id}` : `new-${image.file.name}`
|
||||
}
|
||||
|
||||
function formatFileName(fileName: string): string {
|
||||
const maxLength = 39 // Ajustez cette valeur selon vos besoins
|
||||
const extension = fileName.split('.').pop()
|
||||
const nameWithoutExtension = fileName.slice(0, -(extension!.length + 1))
|
||||
|
||||
if (fileName.length <= maxLength) {
|
||||
return fileName
|
||||
}
|
||||
|
||||
const truncatedName = nameWithoutExtension.slice(-maxLength + extension!.length + 3)
|
||||
return `...${truncatedName}.${extension}`
|
||||
}
|
||||
|
||||
// Carousel settings
|
||||
|
||||
// Carousel settings
|
||||
const settings = {
|
||||
itemsToShow: 2.5,
|
||||
snapAlign: 'center',
|
||||
wrapAround: true,
|
||||
transition: 500,
|
||||
}
|
||||
|
||||
const adjustedSettings = computed(() => {
|
||||
const imageCount = combinedImages.value.length
|
||||
if (imageCount === 1) {
|
||||
return { ...settings, itemsToShow: 1, wrapAround: false }
|
||||
} else {
|
||||
return settings
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.carousel__item {
|
||||
min-height: 310px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.carousel-image {
|
||||
height: 310px;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.carousel__slide {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.carousel__viewport {
|
||||
perspective: 2000px;
|
||||
}
|
||||
|
||||
.carousel__track {
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
.carousel__slide--sliding {
|
||||
transition: 0.5s;
|
||||
}
|
||||
|
||||
.carousel__slide {
|
||||
opacity: 0.9;
|
||||
transform: rotateY(-20deg) scale(0.9);
|
||||
}
|
||||
|
||||
.carousel__slide--active ~ .carousel__slide {
|
||||
transform: rotateY(20deg) scale(0.9);
|
||||
}
|
||||
|
||||
.carousel__slide--active {
|
||||
opacity: 1;
|
||||
transform: rotateY(0) scale(1.1);
|
||||
}
|
||||
|
||||
.carousel-nav-button {
|
||||
background-color: var(--color-gray-50);
|
||||
border-radius: var(--radius-full);
|
||||
padding: 10px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: background-color var(--duration-base) var(--ease-in-out);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.carousel-nav-button:hover {
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.carousel-nav-button svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.carousel-nav-button-prev {
|
||||
left: -30px; /* Adjust as needed */
|
||||
}
|
||||
|
||||
.carousel-nav-button-next {
|
||||
right: -30px; /* Adjust as needed */
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user