191 lines
4.3 KiB
Vue
191 lines
4.3 KiB
Vue
<template>
|
|
<div>
|
|
<button
|
|
class="fixed bottom-4 right-4 bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-full shadow-lg z-50 transition-colors"
|
|
title="Ranger les cartes"
|
|
@click="arrangeCards"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M4 6h16M4 12h16m-7 6h7"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
|
|
<div ref="deck" class="deck">
|
|
<card
|
|
v-for="track in tracks"
|
|
:key="track.id"
|
|
:track="track"
|
|
:class="['card', 'id-' + track.id, { dragging: dragging === track }]"
|
|
:style="{
|
|
top: track.y + 'px',
|
|
left: track.x + 'px',
|
|
zIndex: track.zIndex || 1,
|
|
transform: `rotate(${track.rotation || 0}deg)`
|
|
}"
|
|
:is-face-up="track.isFaceUp"
|
|
@mousedown="startDrag($event, track)"
|
|
@click="flipCard(track)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { useDataStore } from '~/store/data'
|
|
|
|
const tracks = ref([])
|
|
const deck = ref(null)
|
|
let zIndexCounter = 1
|
|
|
|
let dragging = null
|
|
let offset = { x: 0, y: 0 }
|
|
let velocity = { x: 0, y: 0 }
|
|
let lastTime = 0
|
|
|
|
const dataStore = useDataStore()
|
|
|
|
definePageMeta({ layout: 'default' })
|
|
|
|
onMounted(async () => {
|
|
await dataStore.loadData()
|
|
tracks.value = dataStore.getTracksByboxId('ES2025').map((t, i) => ({
|
|
...t,
|
|
x: t.x ?? 50 + i * 20,
|
|
y: t.y ?? 50 + i * 20,
|
|
zIndex: t.zIndex ?? i + 1,
|
|
rotation: 0,
|
|
isFaceUp: t.isFaceUp ?? false
|
|
}))
|
|
|
|
window.addEventListener('mousemove', onDrag)
|
|
window.addEventListener('mouseup', stopDrag)
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('mousemove', onDrag)
|
|
window.removeEventListener('mouseup', stopDrag)
|
|
})
|
|
|
|
function startDrag(e, track) {
|
|
dragging = track
|
|
const rect = deck.value.getBoundingClientRect()
|
|
offset.x = e.clientX - rect.left - track.x
|
|
offset.y = e.clientY - rect.top - track.y
|
|
lastTime = performance.now()
|
|
velocity = { x: 0, y: 0 }
|
|
|
|
zIndexCounter += 1
|
|
track.zIndex = zIndexCounter
|
|
}
|
|
|
|
function onDrag(e) {
|
|
if (!dragging) return
|
|
|
|
const rect = deck.value.getBoundingClientRect()
|
|
const newX = e.clientX - rect.left - offset.x
|
|
const newY = e.clientY - rect.top - offset.y
|
|
|
|
const now = performance.now()
|
|
const dt = now - lastTime
|
|
velocity.x = (newX - dragging.x) / dt
|
|
velocity.y = (newY - dragging.y) / dt
|
|
lastTime = now
|
|
|
|
dragging.x = newX
|
|
dragging.y = newY
|
|
|
|
// Rotation dynamique selon la position horizontale
|
|
const centerX = rect.width / 2
|
|
const dx = dragging.x - centerX
|
|
dragging.rotation = dx * 0.05
|
|
}
|
|
|
|
function stopDrag() {
|
|
if (!dragging) return
|
|
const track = dragging
|
|
dragging = null
|
|
|
|
// Inertie douce
|
|
const decay = 0.95
|
|
function animateInertia() {
|
|
if (Math.abs(velocity.x) < 0.02 && Math.abs(velocity.y) < 0.02) return
|
|
track.x += velocity.x * 16
|
|
track.y += velocity.y * 16
|
|
velocity.x *= decay
|
|
velocity.y *= decay
|
|
requestAnimationFrame(animateInertia)
|
|
}
|
|
animateInertia()
|
|
}
|
|
|
|
function flipCard(track) {
|
|
track.isFaceUp = true
|
|
}
|
|
|
|
function arrangeCards() {
|
|
const deckRect = deck.value.getBoundingClientRect()
|
|
const cardWidth = 224
|
|
const cardHeight = 320
|
|
const padding = 20
|
|
const cardsPerRow = Math.max(
|
|
1,
|
|
Math.floor((deckRect.width - padding * 2) / (cardWidth + padding))
|
|
)
|
|
|
|
tracks.value.forEach((track, index) => {
|
|
const row = Math.floor(index / cardsPerRow)
|
|
const col = index % cardsPerRow
|
|
|
|
track.x = padding + col * (cardWidth + padding)
|
|
track.y = padding + row * (cardHeight / 3)
|
|
track.zIndex = index + 1
|
|
track.rotation = 0
|
|
|
|
zIndexCounter = Math.max(zIndexCounter, track.zIndex)
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.deck {
|
|
position: relative;
|
|
width: 100%;
|
|
min-height: 80vh;
|
|
height: 100%;
|
|
overflow-y: auto;
|
|
padding: 1rem;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.card {
|
|
position: absolute;
|
|
cursor: grab;
|
|
transition:
|
|
transform 0.1s ease,
|
|
box-shadow 0.2s ease;
|
|
}
|
|
|
|
.card:active {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
.card.dragging {
|
|
z-index: 999;
|
|
cursor: grabbing;
|
|
transform: scale(1.05) rotate(0deg);
|
|
/* rotation sera remplacée dynamiquement */
|
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.5);
|
|
}
|
|
</style>
|