frontend/app/app.vue

357 lines
12 KiB
Vue
Raw Permalink Normal View History

2025-09-10 23:43:41 +00:00
<script setup>
import Taskbar from '~/components/Taskbar.vue';
import AboutMeWindow from '~/components/AboutMeWindow.vue';
2025-09-11 14:22:41 +00:00
import TerminalWindow from '~/components/TerminalWindow.vue';
2025-09-11 16:50:28 +00:00
import WinampWindow from '~/components/WinampWindow.vue';
2025-09-12 15:41:20 +00:00
import StickyNoteWindow from '~/components/StickyNoteWindow.vue';
2025-09-11 16:50:28 +00:00
import { ref, reactive, onMounted, computed, provide, onUnmounted } from 'vue';
2025-09-11 11:35:49 +00:00
2025-09-11 16:50:28 +00:00
// --- Data Definitions ---
2025-09-10 23:43:41 +00:00
// Reactive array to hold all window states
const windows = reactive([]);
2025-09-11 16:50:28 +00:00
// Keep track of the highest z-index
2025-09-10 23:43:41 +00:00
const currentZIndex = ref(100);
2025-09-11 16:50:28 +00:00
// Desktop icons container position
const iconsContainerX = ref(0);
const iconsContainerY = ref(0);
2025-09-10 23:43:41 +00:00
2025-09-11 16:50:28 +00:00
// Dragging state for desktop icons container
const isIconsContainerDragging = ref(false);
const iconsContainerStartX = ref(0);
const iconsContainerStartY = ref(0);
const initialIconsContainerX = ref(0);
const initialIconsContainerY = ref(0);
2025-09-10 23:43:41 +00:00
2025-09-11 16:50:28 +00:00
// Define Start Menu Items with icons and actions
const startMenuItems = ref([
{
labelKey: 'terminal',
2025-09-12 15:41:20 +00:00
icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMjEgM0gyVjIxSDIxVjNaIiBmaWxsPSJibGFjayIvPjxwYXRoIGQ9Ik0zIDRWMjBIMjBWM0gzVjRaTTQgNEg1VjVINFY0Wk01IDVIOFY2SDVWNVoiIGZpbGw9ImJsYWNrIi8+PHBhdGggZD0iTTQgNUg1VjZINFY1WiIgZmlsbD0id2hpdGUiLz48cGF0aCBkPSJNNiA5TDkgMTJMNiAxNUg4TDEwIDEyLjVMOCAxMEg2VjlaIiBmaWxsPSJ3aGl0ZSIvPjxwYXthIGQ9Ik0xMSAxNUgxM1YxNkgxMVYxNVoiIGZpbGw9IndoaXRlIi8+PC9zdmc+',
2025-09-11 16:50:28 +00:00
action: 'openTerminal',
},
{
labelKey: 'about_me',
icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIiBmaWxsPSIjRkZGRjAwIi8+CiAgICA8cGF0aCBkPSJNMTEgN0gxM1Y5SDExVjdaTTExIDExSDEzVjE3SDExVjExWiIgZmlsbD0iYmxhY2siLz4KPC9zdmc+Cg==',
action: 'openAboutMe',
},
{
labelKey: 'close_all_windows',
2025-09-11 17:01:54 +00:00
icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8bGluZSB4MT0iNiIgeTE9IjYiIHgyPSIxOCIgeTI9IjE4IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjIiLz4KICA8bGluZSB4MT0iMTgiIHkxPSI2IiB4Mj0iNiIgeTI9IjE4IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+Cg==',
2025-09-11 16:50:28 +00:00
action: 'closeAllWindows',
},
]);
// --- Window Management Functions ---
const generateUniqueId = () => Date.now().toString(36) + Math.random().toString(36).substr(2);
const saveWindowsToLocalStorage = () => localStorage.setItem('windows', JSON.stringify(windows));
2025-09-10 23:43:41 +00:00
2025-09-12 15:41:20 +00:00
const openWindow = (type, titleKey, initialWidth = 600, initialHeight = 400, x = null, y = null) => {
2025-09-10 23:43:41 +00:00
const existingWindow = windows.find(w => w.type === type);
if (existingWindow) {
bringWindowToFront(existingWindow.id);
2025-09-11 16:50:28 +00:00
if (existingWindow.isMinimized) restoreWindow(existingWindow.id);
existingWindow.isVisible = true;
2025-09-10 23:43:41 +00:00
} else {
currentZIndex.value++;
const desktopWidth = window.innerWidth;
2025-09-11 16:50:28 +00:00
const desktopHeight = window.innerHeight - 40;
2025-09-12 15:41:20 +00:00
const initialX = x !== null ? x : (desktopWidth - initialWidth) / 2;
const initialY = y !== null ? y : (desktopHeight - initialHeight) / 2 + 40;
2025-09-10 23:43:41 +00:00
2025-09-11 16:50:28 +00:00
windows.push({
2025-09-10 23:43:41 +00:00
id: generateUniqueId(),
2025-09-11 16:50:28 +00:00
type, title: titleKey, isVisible: true, isMinimized: false,
x: initialX, y: initialY, width: initialWidth, height: initialHeight,
2025-09-10 23:43:41 +00:00
zIndex: currentZIndex.value,
2025-09-11 16:50:28 +00:00
});
2025-09-10 23:43:41 +00:00
}
2025-09-11 16:50:28 +00:00
saveWindowsToLocalStorage();
2025-09-10 23:43:41 +00:00
};
const closeWindow = (id) => {
const index = windows.findIndex(w => w.id === id);
if (index !== -1) {
windows.splice(index, 1);
saveWindowsToLocalStorage();
}
};
const closeAllWindows = () => {
2025-09-12 15:41:20 +00:00
const windowsToKeep = windows.filter(w => w.type === 'sticky-note');
2025-09-11 16:50:28 +00:00
windows.length = 0;
2025-09-12 15:41:20 +00:00
windows.push(...windowsToKeep);
2025-09-10 23:43:41 +00:00
saveWindowsToLocalStorage();
};
const minimizeWindow = (id) => {
2025-09-11 16:50:28 +00:00
const win = windows.find(w => w.id === id);
if (win) {
console.log(`Minimizing window ${id}. Before: isMinimized=${win.isMinimized}`);
win.isMinimized = true;
console.log(`Minimizing window ${id}. After: isMinimized=${win.isMinimized}`);
2025-09-10 23:43:41 +00:00
saveWindowsToLocalStorage();
}
};
const restoreWindow = (id) => {
2025-09-11 16:50:28 +00:00
const win = windows.find(w => w.id === id);
if (win) {
console.log(`Restoring window ${id}. Before: isMinimized=${win.isMinimized}`);
win.isMinimized = false;
console.log(`Restoring window ${id}. After: isMinimized=${win.isMinimized}`);
2025-09-10 23:43:41 +00:00
bringWindowToFront(id);
saveWindowsToLocalStorage();
}
};
const bringWindowToFront = (id) => {
2025-09-11 16:50:28 +00:00
const win = windows.find(w => w.id === id);
if (win) {
2025-09-10 23:43:41 +00:00
currentZIndex.value++;
2025-09-11 16:50:28 +00:00
win.zIndex = currentZIndex.value;
console.log(`Bringing window ${id} to front. New zIndex: ${win.zIndex}`);
2025-09-10 23:43:41 +00:00
saveWindowsToLocalStorage();
}
};
2025-09-11 16:50:28 +00:00
const updateWindowPosition = (id, { x, y }) => {
const win = windows.find(w => w.id === id);
if (win) {
win.x = x;
win.y = y;
2025-09-10 23:43:41 +00:00
saveWindowsToLocalStorage();
}
};
2025-09-11 16:50:28 +00:00
const updateWindowDimensions = (id, { width, height }) => {
const win = windows.find(w => w.id === id);
if (win) {
win.width = width;
win.height = height;
saveWindowsToLocalStorage();
}
2025-09-10 23:43:41 +00:00
};
2025-09-11 16:50:28 +00:00
// --- Action Handlers ---
const openAboutMeWindow = () => openWindow('about-me', 'about_me');
const openTerminalWindow = () => openWindow('terminal', 'terminal');
const openWinampWindow = () => {
console.log('Opening Winamp window via provide/inject.');
openWindow('winamp', 'Winamp', 400, 200);
2025-09-11 14:22:41 +00:00
};
2025-09-11 16:50:28 +00:00
// Provide the openWinampWindow function
provide('openWinamp', openWinampWindow);
const menuActions = {
openTerminal: openTerminalWindow,
openAboutMe: openAboutMeWindow,
closeAllWindows: closeAllWindows,
};
const handleMenuAction = (action) => {
const func = menuActions[action];
if (func) func();
};
// --- Desktop Icons Container Dragging Functions ---
const handleIconsContainerMouseDown = (e) => {
// Only drag if not clicking on a window or taskbar
if (e.target.closest('.window-wrapper') || e.target.closest('.taskbar')) {
console.log('Clicked on a window or taskbar, not dragging desktop icons container.');
return;
}
isIconsContainerDragging.value = true;
iconsContainerStartX.value = e.clientX;
iconsContainerStartY.value = e.clientY;
initialIconsContainerX.value = iconsContainerX.value;
initialIconsContainerY.value = iconsContainerY.value;
console.log(`Icons container drag started. Initial pos: (${initialIconsContainerX.value}, ${initialIconsContainerY.value})`);
};
const handleIconsContainerMouseMove = (e) => {
if (!isIconsContainerDragging.value) return;
const dx = e.clientX - iconsContainerStartX.value;
const dy = e.clientY - iconsContainerStartY.value;
let newX = initialIconsContainerX.value + dx;
let newY = initialIconsContainerY.value + dy;
// Get desktop content dimensions
const desktopContent = document.querySelector('.desktop-content');
const desktopWidth = desktopContent.offsetWidth;
const desktopHeight = desktopContent.offsetHeight;
// Get NuxtPage (icons container) dimensions
const nuxtPage = document.querySelector('.desktop-icons-container');
const nuxtPageWidth = nuxtPage.offsetWidth;
const nuxtPageHeight = nuxtPage.offsetHeight;
// Define boundaries
const minX = -(nuxtPageWidth - desktopWidth);
const minY = -(nuxtPageHeight - desktopHeight);
const maxX = 0;
const maxY = 0;
// Clamp newX and newY within boundaries
iconsContainerX.value = Math.max(minX, Math.min(maxX, newX));
iconsContainerY.value = Math.max(minY, Math.min(maxY, newY));
console.log(`Icons container dragging. Current pos: (${iconsContainerX.value}, ${iconsContainerY.value})`);
};
const handleIconsContainerMouseUp = () => {
if (isIconsContainerDragging.value) {
isIconsContainerDragging.value = false;
console.log(`Icons container drag ended. Final pos: (${iconsContainerX.value}, ${iconsContainerY.value})`);
}
};
// --- Lifecycle Hooks ---
onMounted(() => {
const savedWindows = localStorage.getItem('windows');
if (savedWindows) {
const parsedWindows = JSON.parse(savedWindows);
parsedWindows.forEach(win => {
if (win.zIndex > currentZIndex.value) currentZIndex.value = win.zIndex;
windows.push(win);
});
}
2025-09-12 15:41:20 +00:00
// Automatically open a sticky note if one doesn't exist from localStorage
const stickyNoteExists = windows.some(win => win.type === 'sticky-note');
if (!stickyNoteExists) {
const width = 750; // Updated width
const height = 350; // Updated height
const x = window.innerWidth - width - 20;
const y = 40 + 20; // Taskbar height + padding
openWindow('sticky-note', 'Sticky Note', width, height, x, y);
}
2025-09-11 16:50:28 +00:00
window.addEventListener('mousemove', handleIconsContainerMouseMove);
window.addEventListener('mouseup', handleIconsContainerMouseUp);
2025-09-10 23:43:41 +00:00
});
onUnmounted(() => {
2025-09-11 16:50:28 +00:00
window.removeEventListener('mousemove', handleIconsContainerMouseMove);
window.removeEventListener('mouseup', handleIconsContainerMouseUp);
2025-09-10 23:43:41 +00:00
});
2025-09-11 16:50:28 +00:00
const minimizedWindows = computed(() => windows.filter(win => win.isMinimized));
2025-09-10 23:43:41 +00:00
</script>
<template>
<div>
<Taskbar
2025-09-11 16:50:28 +00:00
:menu-items="startMenuItems"
2025-09-10 23:43:41 +00:00
:minimized-windows="minimizedWindows"
2025-09-11 16:50:28 +00:00
@menu-action="handleMenuAction"
2025-09-10 23:43:41 +00:00
@restore-window="restoreWindow"
/>
<div class="desktop-content">
2025-09-11 16:50:28 +00:00
<div
class="desktop-icons-container"
:style="{ left: `${iconsContainerX}px`, top: `${iconsContainerY}px`, cursor: isIconsContainerDragging ? 'grabbing' : 'grab' }"
@mousedown="handleIconsContainerMouseDown"
>
<NuxtPage />
</div>
2025-09-10 23:43:41 +00:00
</div>
<template v-for="window in windows" :key="window.id">
<AboutMeWindow
v-if="window.type === 'about-me' && window.isVisible && !window.isMinimized"
:window-data="window"
@close="closeWindow(window.id)"
@minimize="minimizeWindow(window.id)"
@bring-to-front="bringWindowToFront(window.id)"
2025-09-11 16:50:28 +00:00
@update-position="updateWindowPosition(window.id, $event)"
@update-dimensions="updateWindowDimensions(window.id, $event)"
2025-09-10 23:43:41 +00:00
/>
2025-09-11 14:22:41 +00:00
<TerminalWindow
v-if="window.type === 'terminal' && window.isVisible && !window.isMinimized"
:window-data="window"
@close="closeWindow(window.id)"
@minimize="minimizeWindow(window.id)"
@bring-to-front="bringWindowToFront(window.id)"
2025-09-11 16:50:28 +00:00
@update-position="updateWindowPosition(window.id, $event)"
@update-dimensions="updateWindowDimensions(window.id, $event)"
/>
<WinampWindow
v-if="window.type === 'winamp' && window.isVisible && !window.isMinimized"
:window-data="window"
@close="closeWindow(window.id)"
@minimize="minimizeWindow(window.id)"
@bring-to-front="bringWindowToFront(window.id)"
@update-position="updateWindowPosition(window.id, $event)"
@update-dimensions="updateWindowDimensions(window.id, $event)"
2025-09-11 14:22:41 +00:00
/>
2025-09-12 15:41:20 +00:00
<StickyNoteWindow
v-if="window.type === 'sticky-note' && window.isVisible && !window.isMinimized"
:window-data="window"
@close="closeWindow(window.id)"
@minimize="minimizeWindow(window.id)"
@bring-to-front="bringWindowToFront(window.id)"
@update-dimensions="updateWindowDimensions(window.id, $event)"
/>
2025-09-10 23:43:41 +00:00
</template>
</div>
</template>
<style>
2025-09-11 16:50:28 +00:00
html {
background-color: #008080;
background-image: url('https://picsum.photos/1920/1080');
2025-09-10 23:43:41 +00:00
background-size: cover;
background-position: center;
background-repeat: no-repeat;
2025-09-11 16:50:28 +00:00
height: 100vh; /* Use vh for full viewport height */
}
html, body {
2025-09-10 23:43:41 +00:00
margin: 0;
2025-09-11 16:50:28 +00:00
padding: 0;
}
body {
height: 100vh; /* Use vh for full viewport height */
font-family: 'Courier New', Courier, monospace;
2025-09-10 23:43:41 +00:00
}
.taskbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 40px;
2025-09-11 16:50:28 +00:00
z-index: 1000;
2025-09-10 23:43:41 +00:00
}
.desktop-content {
2025-09-11 16:50:28 +00:00
position: absolute;
top: 40px;
left: 0;
2025-09-10 23:43:41 +00:00
width: 100%;
2025-09-11 16:50:28 +00:00
height: calc(100vh - 40px); /* Changed to 100vh - 40px */
2025-09-10 23:43:41 +00:00
box-sizing: border-box;
2025-09-11 16:50:28 +00:00
background-color: transparent;
border: none;
box-shadow: none;
overflow: hidden; /* Prevent scrollbars on the desktop content itself */
}
.desktop-icons-container {
position: relative; /* Allow positioning of children (NuxtPage) */
width: 100%;
height: 100%;
cursor: grab;
2025-09-10 23:43:41 +00:00
}
2025-09-11 16:50:28 +00:00
</style>