frontend/app/components/BaseWindow.vue

208 lines
6.3 KiB
Vue
Raw Normal View History

2025-09-11 16:50:28 +00:00
<template>
<div
class="window-wrapper"
:style="{ top: windowData.y + 'px', left: windowData.x + 'px', width: windowData.width + 'px', height: windowData.height + 'px', zIndex: windowData.zIndex }"
@mousedown="bringToFront"
>
<div class="title-bar" @mousedown.prevent="startDrag">
<span class="title">{{ t(windowData.title) }}</span>
<div class="window-controls">
<button @click.stop="minimize">_</button>
<button @click.stop="close">X</button>
</div>
</div>
<div class="content">
<slot></slot> <!-- Unique content will be injected here -->
</div>
<!-- Resize Handles -->
<div class="resize-handle top" @mousedown.prevent="startResize('top')"></div>
<div class="resize-handle bottom" @mousedown.prevent="startResize('bottom')"></div>
<div class="resize-handle left" @mousedown.prevent="startResize('left')"></div>
<div class="resize-handle right" @mousedown.prevent="startResize('right')"></div>
<div class="resize-handle top-left" @mousedown.prevent="startResize('top-left')"></div>
<div class="resize-handle top-right" @mousedown.prevent="startResize('top-right')"></div>
<div class="resize-handle bottom-left" @mousedown.prevent="startResize('bottom-left')"></div>
<div class="resize-handle bottom-right" @mousedown.prevent="startResize('bottom-right')"></div>
</div>
</template>
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({ windowData: Object });
const emit = defineEmits(['close', 'minimize', 'bring-to-front', 'update-position', 'update-dimensions']);
const close = () => emit('close');
const minimize = () => emit('minimize');
const bringToFront = () => emit('bring-to-front');
const startDrag = (event) => {
const startX = event.clientX;
const startY = event.clientY;
const initialX = props.windowData.x;
const initialY = props.windowData.y;
const onMouseMove = (moveEvent) => {
let newX = initialX + (moveEvent.clientX - startX);
let newY = initialY + (moveEvent.clientY - startY);
// Get window dimensions
const windowWidth = props.windowData.width;
const windowHeight = props.windowData.height;
// Define boundaries
const minX = 0;
const minY = 40; // Taskbar height
const maxX = window.innerWidth - windowWidth;
const maxY = window.innerHeight - windowHeight;
// Clamp newX and newY within boundaries
newX = Math.max(minX, Math.min(maxX, newX));
newY = Math.max(minY, Math.min(maxY, newY));
emit('update-position', { x: newX, y: newY });
};
const onMouseUp = () => {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
};
const startResize = (handle) => {
const startX = event.clientX;
const startY = event.clientY;
const initialX = props.windowData.x;
const initialY = props.windowData.y;
const initialWidth = props.windowData.width;
const initialHeight = props.windowData.height;
const minWidth = 200;
const minHeight = 150;
const onMouseMove = (moveEvent) => {
const deltaX = moveEvent.clientX - startX;
const deltaY = moveEvent.clientY - startY;
let newX = initialX;
let newY = initialY;
let newWidth = initialWidth;
let newHeight = initialHeight;
if (handle.includes('right')) {
newWidth = Math.max(minWidth, initialWidth + deltaX);
}
if (handle.includes('left')) {
const calculatedWidth = initialWidth - deltaX;
if (calculatedWidth >= minWidth) {
newWidth = calculatedWidth;
newX = initialX + deltaX;
}
}
if (handle.includes('bottom')) {
newHeight = Math.max(minHeight, initialHeight + deltaY);
}
if (handle.includes('top')) {
const calculatedHeight = initialHeight - deltaY;
if (calculatedHeight >= minHeight) {
newHeight = calculatedHeight;
newY = initialY + deltaY;
}
}
// Clamp position and dimensions during resize
const currentWindowRight = newX + newWidth;
const currentWindowBottom = newY + newHeight;
const maxAllowedX = window.innerWidth - newWidth;
const maxAllowedY = window.innerHeight - newHeight;
newX = Math.max(0, Math.min(maxAllowedX, newX));
newY = Math.max(40, Math.min(maxAllowedY, newY)); // 40px for taskbar
emit('update-position', { x: newX, y: newY });
emit('update-dimensions', { width: newWidth, height: newHeight });
};
const onMouseUp = () => {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
};
</script>
<style scoped>
.window-wrapper {
position: absolute;
background-color: #c0c0c0;
border: 1px solid;
border-color: #ffffff #808080 #808080 #ffffff;
box-shadow: 1px 1px 4px rgba(0,0,0,0.5);
display: flex;
flex-direction: column;
min-width: 200px;
min-height: 150px;
}
.title-bar {
background-color: #000080;
color: white;
padding: 4px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: move;
font-weight: bold;
}
.window-controls button {
background-color: #c0c0c0;
border: 1px solid;
border-color: #ffffff #808080 #808080 #ffffff;
font-weight: bold;
margin-left: 2px;
min-width: 20px;
}
.content {
/* padding: 10px; */ /* REMOVED PADDING */
flex-grow: 1;
overflow: auto;
}
.resize-handle {
position: absolute;
z-index: 10;
}
.resize-handle.top {
top: -4px; left: 4px; right: 4px; height: 8px; cursor: ns-resize;
}
.resize-handle.bottom {
bottom: -4px; left: 4px; right: 4px; height: 8px; cursor: ns-resize;
}
.resize-handle.left {
left: -4px; top: 4px; bottom: 4px; width: 8px; cursor: ew-resize;
}
.resize-handle.right {
right: -4px; top: 4px; bottom: 4px; width: 8px; cursor: ew-resize;
}
.resize-handle.top-left {
top: -4px; left: -4px; width: 16px; height: 16px; cursor: nwse-resize;
}
.resize-handle.top-right {
top: -4px; right: -4px; width: 16px; height: 16px; cursor: nesw-resize;
}
.resize-handle.bottom-left {
bottom: -4px; left: -4px; width: 16px; height: 16px; cursor: nesw-resize;
}
.resize-handle.bottom-right {
bottom: -4px; right: -4px; width: 16px; height: 16px; cursor: nwse-resize;
}
</style>