@@ -222,11 +228,55 @@ function onMouseDown() {
cursor: pointer;
font-size: 12px;
line-height: 1;
+ transition: all 0.15s ease;
+ position: relative;
}
-.minimize { background: var(--control-btn-minimize-bg); }
-.maximize { background: var(--control-btn-maximize-bg); }
-.close { background: var(--control-btn-close-bg); }
+.control-btn:hover {
+ transform: scale(1.1);
+ opacity: 0.9;
+}
+
+.control-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ font-weight: bold;
+ font-size: 11px;
+ line-height: 1;
+}
+
+.minimize {
+ background: var(--control-btn-minimize-bg);
+ color: #000;
+}
+
+.minimize:hover {
+ background: #f39c12;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+}
+
+.maximize {
+ background: var(--control-btn-maximize-bg);
+ color: #000;
+}
+
+.maximize:hover {
+ background: #27ae60;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+}
+
+.close {
+ background: var(--control-btn-close-bg);
+ color: #fff;
+}
+
+.close:hover {
+ background: #c0392b;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+}
.content {
flex-grow: 1;
diff --git a/composables/useBreakpoint.ts b/composables/useBreakpoint.ts
index b2fdc79..8f7adcf 100644
--- a/composables/useBreakpoint.ts
+++ b/composables/useBreakpoint.ts
@@ -13,7 +13,9 @@ export function useBreakpoint() {
onMounted(() => {
update();
- window.addEventListener('resize', update);
+ if (typeof window !== 'undefined') {
+ window.addEventListener('resize', update);
+ }
});
onUnmounted(() => {
diff --git a/composables/useDraggable.ts b/composables/useDraggable.ts
index 8a4b8f7..824c28b 100644
--- a/composables/useDraggable.ts
+++ b/composables/useDraggable.ts
@@ -53,12 +53,14 @@ export function useDraggable(target: Ref
, options: UseDragga
let newY = startY + dy;
let snapType: SnapType = null;
- if (event.clientX <= 1) {
- snapType = 'left';
- } else if (event.clientX >= window.innerWidth - 1) {
- snapType = 'right';
- } else if (event.clientY <= 1) {
- snapType = 'top';
+ if (typeof window !== 'undefined') {
+ if (event.clientX <= 1) {
+ snapType = 'left';
+ } else if (event.clientX >= window.innerWidth - 1) {
+ snapType = 'right';
+ } else if (event.clientY <= 1) {
+ snapType = 'top';
+ }
}
if (snapType !== currentSnapType) {
diff --git a/lang/zh-TW.json b/lang/zh-TW.json
index 024837a..81d528c 100644
--- a/lang/zh-TW.json
+++ b/lang/zh-TW.json
@@ -8,7 +8,7 @@
},
"taskbar": {
"language": "語言",
- "currentLanguage": "中"
+ "currentLanguage": "注"
},
"common": {
"createWindow": "建立視窗",
diff --git a/stores/apps.ts b/stores/apps.ts
new file mode 100644
index 0000000..a31bdfb
--- /dev/null
+++ b/stores/apps.ts
@@ -0,0 +1,166 @@
+import { ref, computed } from 'vue';
+import { defineStore } from 'pinia';
+
+export interface AppInfo {
+ id: string;
+ name: string;
+ icon: string;
+ component: string;
+ description: string;
+ category: string;
+}
+
+export interface AppInstance {
+ id: string;
+ appId: string;
+ title: string;
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ zIndex: number;
+ isMinimized: boolean;
+ isMaximized: boolean;
+ isFocused: boolean;
+}
+
+let appInstanceIdCounter = 0;
+
+export const useAppsStore = defineStore('apps', () => {
+ // Available apps registry
+ const availableApps = ref([
+ {
+ id: 'calculator',
+ name: 'Calculator',
+ icon: '🧮',
+ component: 'Calculator',
+ description: 'A simple calculator for basic arithmetic operations',
+ category: 'Utilities'
+ },
+ // More apps can be added here in the future
+ ]);
+
+ // Running app instances
+ const appInstances = ref([]);
+ const nextZIndex = ref(100);
+
+ // Getters
+ const getAppById = computed(() => {
+ return (id: string) => availableApps.value.find(app => app.id === id);
+ });
+
+ const getAppInstanceById = computed(() => {
+ return (id: string) => appInstances.value.find(instance => instance.id === id);
+ });
+
+ const orderedAppInstances = computed(() => {
+ return [...appInstances.value].sort((a, b) => a.zIndex - b.zIndex);
+ });
+
+ const focusedAppInstance = computed(() => {
+ return appInstances.value.find(instance => instance.isFocused);
+ });
+
+ // Actions
+ function launchApp(appId: string) {
+ const app = getAppById.value(appId);
+ if (!app) {
+ console.error(`App with id "${appId}" not found`);
+ return null;
+ }
+
+ const newInstanceId = appInstanceIdCounter++;
+ const newInstance: AppInstance = {
+ id: `app-${appId}-${newInstanceId}`,
+ appId: appId,
+ title: `${app.name} #${newInstanceId + 1}`,
+ x: Math.random() * 200 + 50,
+ y: Math.random() * 100 + 50 + 48, // Below taskbar
+ width: 320,
+ height: 400,
+ zIndex: nextZIndex.value++,
+ isMinimized: false,
+ isMaximized: false,
+ isFocused: true,
+ };
+
+ // Unfocus all other instances
+ appInstances.value.forEach(instance => instance.isFocused = false);
+
+ appInstances.value.push(newInstance);
+ return newInstance;
+ }
+
+ function focusAppInstance(instanceId: string) {
+ const instance = appInstances.value.find(i => i.id === instanceId);
+ if (!instance || instance.isFocused) return;
+
+ // Unfocus all other instances
+ appInstances.value.forEach(i => i.isFocused = false);
+
+ instance.zIndex = nextZIndex.value++;
+ instance.isFocused = true;
+
+ if (instance.isMinimized) {
+ instance.isMinimized = false;
+ }
+ }
+
+ function closeAppInstance(instanceId: string) {
+ appInstances.value = appInstances.value.filter(i => i.id !== instanceId);
+ }
+
+ function minimizeAppInstance(instanceId: string) {
+ const instance = appInstances.value.find(i => i.id === instanceId);
+ if (instance) {
+ instance.isMinimized = true;
+ instance.isFocused = false;
+ }
+ }
+
+ function toggleMaximizeAppInstance(instanceId: string) {
+ const instance = appInstances.value.find(i => i.id === instanceId);
+ if (instance) {
+ instance.isMaximized = !instance.isMaximized;
+ focusAppInstance(instanceId);
+ }
+ }
+
+ function updateAppInstancePosition({ id, x, y }: { id: string; x: number; y: number }) {
+ const instance = appInstances.value.find(i => i.id === id);
+ if (instance && !instance.isMaximized) {
+ instance.x = x;
+ instance.y = y;
+ }
+ }
+
+ function updateAppInstanceSize({ id, width, height }: { id: string; width: number; height: number }) {
+ const instance = appInstances.value.find(i => i.id === id);
+ if (instance && !instance.isMaximized) {
+ instance.width = width;
+ instance.height = height;
+ }
+ }
+
+ function closeAllAppInstances() {
+ appInstances.value = [];
+ }
+
+ return {
+ availableApps,
+ appInstances,
+ nextZIndex,
+ getAppById,
+ getAppInstanceById,
+ orderedAppInstances,
+ focusedAppInstance,
+ launchApp,
+ focusAppInstance,
+ closeAppInstance,
+ minimizeAppInstance,
+ toggleMaximizeAppInstance,
+ updateAppInstancePosition,
+ updateAppInstanceSize,
+ closeAllAppInstances,
+ };
+});
diff --git a/stores/desktop.ts b/stores/desktop.ts
new file mode 100644
index 0000000..555b712
--- /dev/null
+++ b/stores/desktop.ts
@@ -0,0 +1,105 @@
+import { ref, computed } from 'vue';
+import { defineStore } from 'pinia';
+import type { AppInfo } from './apps';
+
+export interface DesktopIconPosition {
+ appId: string;
+ x: number;
+ y: number;
+}
+
+export const useDesktopStore = defineStore('desktop', () => {
+ // Desktop icon positions
+ const iconPositions = ref([]);
+
+ // Default grid positions for new icons
+ const getNextGridPosition = () => {
+ const iconSize = 80;
+ const padding = 20;
+
+ // Check if we're in browser environment
+ const screenWidth = typeof window !== 'undefined' ? window.innerWidth : 1200;
+ const cols = Math.floor((screenWidth - padding) / (iconSize + padding));
+
+ const currentCount = iconPositions.value.length;
+ const row = Math.floor(currentCount / cols);
+ const col = currentCount % cols;
+
+ return {
+ x: padding + col * (iconSize + padding),
+ y: 48 + padding + row * (iconSize + padding) // Below taskbar
+ };
+ };
+
+ // Initialize desktop icons for available apps
+ function initializeDesktopIcons(apps: AppInfo[]) {
+ if (iconPositions.value.length === 0) {
+ apps.forEach((app, index) => {
+ const position = getNextGridPosition();
+ iconPositions.value.push({
+ appId: app.id,
+ x: position.x,
+ y: position.y
+ });
+ });
+ }
+ }
+
+ // Update icon position
+ function updateIconPosition(appId: string, x: number, y: number) {
+ const icon = iconPositions.value.find(pos => pos.appId === appId);
+ if (icon) {
+ icon.x = x;
+ icon.y = y;
+ }
+ }
+
+ // Get icon position
+ function getIconPosition(appId: string) {
+ return iconPositions.value.find(pos => pos.appId === appId);
+ }
+
+ // Add new icon to desktop
+ function addIconToDesktop(appId: string) {
+ const existing = iconPositions.value.find(pos => pos.appId === appId);
+ if (!existing) {
+ const position = getNextGridPosition();
+ iconPositions.value.push({
+ appId,
+ x: position.x,
+ y: position.y
+ });
+ }
+ }
+
+ // Remove icon from desktop
+ function removeIconFromDesktop(appId: string) {
+ iconPositions.value = iconPositions.value.filter(pos => pos.appId !== appId);
+ }
+
+ // Auto-arrange icons in grid
+ function arrangeIconsInGrid() {
+ const iconSize = 80;
+ const padding = 20;
+ const screenWidth = typeof window !== 'undefined' ? window.innerWidth : 1200;
+ const cols = Math.floor((screenWidth - padding) / (iconSize + padding));
+
+ iconPositions.value.forEach((icon, index) => {
+ const row = Math.floor(index / cols);
+ const col = index % cols;
+
+ icon.x = padding + col * (iconSize + padding);
+ icon.y = 48 + padding + row * (iconSize + padding);
+ });
+ }
+
+ return {
+ iconPositions,
+ initializeDesktopIcons,
+ updateIconPosition,
+ getIconPosition,
+ addIconToDesktop,
+ removeIconFromDesktop,
+ arrangeIconsInGrid,
+ };
+});