frontend/app/components/WindowFrame.vue

149 lines
3.7 KiB
Vue

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const props = defineProps({
windowData: Object,
});
const emit = defineEmits([
'close',
'minimize',
'bring-to-front',
'update-position',
'update-dimensions',
]);
const isDragging = ref(false);
const startX = ref(0);
const startY = ref(0);
const initialX = ref(0); // Store initial x position when drag starts
const initialY = ref(0); // Store initial y position when drag starts
const currentX = ref(0); // New ref for current X during drag
const currentY = ref(0); // New ref for current Y during drag
const windowRef = ref(null);
const handleMouseDown = (e) => {
if (e.target.classList.contains('window-header') || e.target.classList.contains('window-title')) {
isDragging.value = true;
startX.value = e.clientX;
startY.value = e.clientY;
initialX.value = props.windowData.x; // Initialize with prop value at drag start
initialY.value = props.windowData.y; // Initialize with prop value at drag start
currentX.value = props.windowData.x; // Initialize current with prop value
currentY.value = props.windowData.y; // Initialize current with prop value
emit('bring-to-front');
}
};
const handleMouseMove = (e) => {
if (!isDragging.value) return;
const dx = e.clientX - startX.value;
const dy = e.clientY - startY.value;
// Update local position during drag based on initial position
currentX.value = initialX.value + dx; // Use initialX, not props.windowData.x
currentY.value = initialY.value + dy; // Use initialY, not props.windowData.y
};
const handleMouseUp = () => {
if (isDragging.value) {
isDragging.value = false;
// Emit final position only once after drag ends
emit('update-position', { x: currentX.value, y: currentY.value });
}
};
onMounted(() => {
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
});
onUnmounted(() => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
});
const closeWindow = () => {
emit('close');
};
const minimizeWindow = () => {
emit('minimize');
};
const bringToFront = () => {
emit('bring-to-front');
};
</script>
<template>
<div
ref="windowRef"
class="window"
:style="{
// Use currentX and currentY for positioning during drag
left: `${isDragging ? currentX : windowData.x}px`,
top: `${isDragging ? currentY : windowData.y}px`,
width: `${windowData.width}px`,
height: `${windowData.height}px`,
zIndex: windowData.zIndex,
}"
@mousedown="bringToFront"
>
<div class="window-header" @mousedown="handleMouseDown">
<span class="window-title">{{ windowData.title }}</span>
<div class="window-controls">
<button @click="minimizeWindow">_</button>
<button @click="closeWindow">X</button>
</div>
</div>
<div class="window-content">
<slot></slot>
</div>
</div>
</template>
<style scoped>
.window {
position: absolute;
border: 2px solid #000;
box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.5);
background-color: #C0C0C0;
display: flex;
flex-direction: column;
resize: both;
overflow: auto;
}
.window-header {
background-color: #000080;
color: white;
padding: 4px;
cursor: grab;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'MS Sans Serif', 'Arial', sans-serif;
font-size: 14px;
}
.window-title {
font-weight: bold;
}
.window-controls button {
background-color: #C0C0C0;
border: 1px solid #000;
padding: 1px 6px;
margin-left: 4px;
cursor: pointer;
font-family: 'MS Sans Serif', 'Arial', sans-serif;
font-size: 12px;
}
.window-content {
flex-grow: 1;
background-color: #C0C0C0;
border-top: 2px solid #000;
}
</style>