designtopack/src/components/project/virtual-sample/Interactive360.vue
2025-02-27 17:08:35 +01:00

222 lines
5.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<figure>
<div
@mousedown="enableDragToRotate"
@touchstart="enableDragToRotate"
class="drag-zone"
:class="{ grabbing: isDragToRotateEnabled }"
></div>
<img
:src="
virtualSampleStore.isCompareModeEnabled
? currentFile.url
: openedFile.url
"
alt=""
width="500"
height="500"
/>
</figure>
<div
id="helper"
class="flex flex-col | bg-black-50"
:hidden="isHelperHidden ? true : undefined"
>
<p class="rounded-lg | text-white bg-grey-800 | p-16">
Vous pouvez visualiser léchantillon sous différents angles en cliquant et
en déplaçant la souris de gauche à droite ou de bas en haut.
</p>
<button class="btn" @click="isHelperHidden = true">Jai compris</button>
</div>
</template>
<script setup>
import throttle from 'lodash/throttle';
import { ref, computed, watch } from 'vue';
import { useVirtualSampleStore } from '../../../stores/virtualSample';
import { storeToRefs } from 'pinia';
import { useDialogStore } from '../../../stores/dialog';
const { activeTrack } = defineProps({
activeTrack: Object,
});
// Helper
const { openedFile } = storeToRefs(useDialogStore());
const virtualSampleStore = useVirtualSampleStore();
const { isDownloadTriggered } = storeToRefs(useVirtualSampleStore());
const isHelperHidden = ref(localStorage.getItem('isHelperHidden'));
localStorage.setItem('isHelperHidden', true);
// Grab interaction
const yMax = computed(() =>
parseInt(activeTrack.files[activeTrack.files.length - 1].name.split('_')[0])
);
const xMax = computed(() =>
parseInt(
activeTrack.files[activeTrack.files.length - 1].name
.split('_')[1]
.split('.')[0]
)
);
const frontXView = (xMax.value + 1) / 2;
const currentX = ref(frontXView);
const currentY = ref(0);
const currentFileIndex = computed(() => currentY.value + '_' + currentX.value);
const currentFile = computed(() =>
activeTrack.files.find((file) => file.name.includes(currentFileIndex.value))
);
watch(
currentFile,
() => {
virtualSampleStore.currentFile = currentFile.value;
openedFile.value = currentFile.value;
},
{ immediate: true }
);
// Rotation
function rotateX(direction) {
if (direction === 'left') {
currentX.value = currentX.value === 0 ? xMax.value : currentX.value - 1;
} else if (direction === 'right') {
currentX.value = currentX.value === xMax.value ? 0 : currentX.value + 1;
}
}
const isDragToRotateEnabled = ref(false);
let previousMouseXPos;
let previousMouseYPos;
const DRAG_STEP = 20;
function enableDragToRotate(event) {
console.log(event);
if (event.type.startsWith('touch')) event.preventDefault();
const isTouch = event.type.startsWith('touch');
isDragToRotateEnabled.value = true;
const clientX = isTouch ? event.touches[0].clientX : event.clientX;
const clientY = isTouch ? event.touches[0].clientY : event.clientY;
previousMouseXPos = clientX;
previousMouseYPos = clientY;
}
function disableDragToRotate() {
isDragToRotateEnabled.value = false;
}
function dragToRotate(event) {
if (event.type.startsWith('touch')) event.preventDefault();
const isTouch = event.type.startsWith('touch');
const clientX = isTouch ? event.touches[0].clientX : event.clientX;
const clientY = isTouch ? event.touches[0].clientY : event.clientY;
handleHorizontalRotation(clientX);
handleVerticalRotation(clientY);
}
function handleHorizontalRotation(clientX) {
if (previousMouseXPos === undefined) {
previousMouseXPos = clientX;
return;
}
if (clientX > previousMouseXPos + DRAG_STEP) {
rotateX('left');
previousMouseXPos = clientX;
} else if (clientX < previousMouseXPos - DRAG_STEP) {
rotateX('right');
previousMouseXPos = clientX;
}
}
function handleVerticalRotation(clientY) {
if (previousMouseYPos === undefined) {
previousMouseYPos = clientY;
return;
}
if (clientY > previousMouseYPos + DRAG_STEP) {
if (currentY.value < yMax.value) {
currentY.value++;
previousMouseYPos = clientY;
}
} else if (clientY < previousMouseYPos - DRAG_STEP) {
if (currentY.value > 0) {
currentY.value--;
previousMouseYPos = clientY;
}
}
}
function resetView() {
currentX.value = frontXView;
currentY.value = 0;
}
const throttledDragToRotate = throttle(dragToRotate, 50);
watch(isDragToRotateEnabled, (newValue) => {
if (newValue) {
window.addEventListener('mousemove', throttledDragToRotate);
window.addEventListener('touchmove', throttledDragToRotate, {
passive: false,
});
} else {
window.removeEventListener('mousemove', throttledDragToRotate);
window.removeEventListener('touchmove', throttledDragToRotate);
previousMouseXPos = undefined;
previousMouseYPos = undefined;
}
});
window.addEventListener('mouseup', disableDragToRotate);
window.addEventListener('touchend', disableDragToRotate);
const { isLoopAnimationEnabled } = storeToRefs(useVirtualSampleStore());
function loopAnimation() {
rotateX('right');
}
let animationIntervalId;
watch(isLoopAnimationEnabled, (newValue) => {
if (newValue) {
animationIntervalId = setInterval(loopAnimation, 300);
} else {
clearInterval(animationIntervalId);
}
});
// Images preload
const imageUrls = computed(() => activeTrack.files.map((file) => file.url));
watch(
imageUrls,
() => {
preloadImages();
resetView();
},
{ immediate: true }
);
function preloadImages() {
imageUrls.value.forEach((imageUrl) => {
const image = new Image();
image.src = imageUrl;
});
}
// Download image
watch(isDownloadTriggered, (newValue) => {
if (!newValue) return;
const downloadNode = document.createElement('a');
downloadNode.setAttribute('href', currentFile.value.source);
downloadNode.setAttribute('download', '');
document.body.appendChild(downloadNode);
downloadNode.click();
document.body.removeChild(downloadNode);
});
</script>