208 lines
6.3 KiB
Vue
208 lines
6.3 KiB
Vue
|
<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>
|