windows/components/Taskbar.vue

253 lines
5.8 KiB
Vue
Raw Normal View History

2025-09-23 16:43:57 +00:00
<script setup lang="ts">
2025-09-25 02:10:57 +00:00
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
2025-09-23 16:43:57 +00:00
import { storeToRefs } from 'pinia';
import { useWindowsStore } from '../stores/windows';
import { useUIStore } from '../stores/ui';
2025-09-25 02:10:57 +00:00
import { useI18n } from 'vue-i18n';
2025-09-23 16:43:57 +00:00
2025-09-25 02:56:23 +00:00
const { t, locale, setLocale } = useI18n();
2025-09-23 16:43:57 +00:00
const windowsStore = useWindowsStore();
const uiStore = useUIStore();
const { windows } = storeToRefs(windowsStore);
const { focusWindow } = windowsStore;
const { toggleStartMenu } = uiStore;
2025-09-24 09:45:36 +00:00
// --- Datetime Logic ---
const dateString = ref('');
const timeString = ref('');
let timer: number;
const updateTime = () => {
const now = new Date();
2025-09-25 02:10:57 +00:00
dateString.value = now.toLocaleDateString(locale.value, { weekday: 'short', month: 'short', day: 'numeric' });
timeString.value = now.toLocaleTimeString(locale.value, { hour: '2-digit', minute: '2-digit', hour12: false });
2025-09-24 09:45:36 +00:00
};
2025-09-25 02:10:57 +00:00
watch(locale, updateTime);
2025-09-24 09:45:36 +00:00
onMounted(() => {
updateTime();
timer = window.setInterval(updateTime, 1000);
});
2025-09-25 02:56:23 +00:00
// --- Language Switcher Logic ---
const isLanguageMenuOpen = ref(false);
const languageSwitcherWrapper = ref<HTMLElement | null>(null);
const availableLanguages = computed(() => [
{ key: 'en', label: 'English', display: 'EN' },
{ key: 'zh', label: '繁體中文', display: '中' },
2025-09-25 02:10:57 +00:00
]);
2025-09-24 09:45:36 +00:00
2025-09-25 02:56:23 +00:00
const currentLanguageDisplay = computed(() => {
const current = availableLanguages.value.find(lang => lang.key === locale.value);
return current?.display || '中';
});
function toggleLanguageMenu() {
isLanguageMenuOpen.value = !isLanguageMenuOpen.value;
2025-09-24 09:45:36 +00:00
}
2025-09-25 02:56:23 +00:00
function selectLanguage(lang: 'en' | 'zh') {
setLocale(lang);
isLanguageMenuOpen.value = false;
2025-09-24 09:45:36 +00:00
}
const handleClickOutside = (event: MouseEvent) => {
2025-09-25 02:56:23 +00:00
if (languageSwitcherWrapper.value && !languageSwitcherWrapper.value.contains(event.target as Node)) {
isLanguageMenuOpen.value = false;
2025-09-24 09:45:36 +00:00
}
};
2025-09-25 02:56:23 +00:00
watch(isLanguageMenuOpen, (isOpen) => {
2025-09-24 09:45:36 +00:00
if (isOpen) {
document.addEventListener('click', handleClickOutside);
} else {
document.removeEventListener('click', handleClickOutside);
}
});
onUnmounted(() => {
clearInterval(timer);
document.removeEventListener('click', handleClickOutside);
});
2025-09-23 16:43:57 +00:00
function handleTaskbarButtonClick(windowId: string) {
focusWindow(windowId);
}
</script>
<template>
<div class="taskbar">
<button @click="toggleStartMenu" class="start-button">🚀</button>
<div class="window-list">
<button
v-for="window in windows"
:key="window.id"
class="taskbar-item"
:class="{ 'is-active': window.isFocused }"
@click="handleTaskbarButtonClick(window.id)"
>
{{ window.title }}
</button>
</div>
2025-09-24 09:45:36 +00:00
<div class="taskbar-right-controls">
2025-09-25 02:56:23 +00:00
<div class="language-switcher-wrapper" ref="languageSwitcherWrapper">
<button @click="toggleLanguageMenu" class="language-switcher">
{{ currentLanguageDisplay }}
2025-09-24 09:45:36 +00:00
</button>
2025-09-25 02:56:23 +00:00
<div v-if="isLanguageMenuOpen" class="language-menu">
2025-09-24 09:45:36 +00:00
<ul>
2025-09-25 02:56:23 +00:00
<li v-for="lang in availableLanguages" :key="lang.key" @click="selectLanguage(lang.key as 'en' | 'zh')">
<span class="checkmark" :style="{ visibility: locale === lang.key ? 'visible' : 'hidden' }"></span>
<span>{{ lang.label }}</span>
2025-09-24 09:45:36 +00:00
</li>
</ul>
</div>
</div>
<div class="datetime">
<span>{{ dateString }}</span>
<span>{{ timeString }}</span>
</div>
</div>
2025-09-23 16:43:57 +00:00
</div>
</template>
<style scoped>
.taskbar {
position: fixed;
2025-09-24 09:45:36 +00:00
top: 0;
2025-09-23 16:43:57 +00:00
left: 0;
right: 0;
2025-09-24 09:45:36 +00:00
height: 22px;
2025-09-23 16:43:57 +00:00
background-color: var(--taskbar-background);
backdrop-filter: blur(10px);
display: flex;
align-items: center;
2025-09-24 09:45:36 +00:00
padding: 0 8px;
2025-09-23 16:43:57 +00:00
z-index: var(--z-taskbar);
2025-09-24 09:45:36 +00:00
color: var(--taskbar-item-text-color);
font-size: 12px;
2025-09-23 16:43:57 +00:00
}
.start-button {
background: none;
border: none;
2025-09-24 09:45:36 +00:00
font-size: 14px;
2025-09-23 16:43:57 +00:00
cursor: pointer;
2025-09-24 09:45:36 +00:00
padding: 0 8px;
color: inherit;
margin-right: 4px;
line-height: 22px;
2025-09-23 16:43:57 +00:00
}
.window-list {
2025-09-24 09:45:36 +00:00
flex: 1 1 0;
min-width: 0;
2025-09-23 16:43:57 +00:00
display: flex;
2025-09-24 09:45:36 +00:00
gap: 4px;
overflow-x: auto;
}
/* Subtle scrollbar styling */
.window-list::-webkit-scrollbar {
height: 2px;
}
.window-list::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.3);
border-radius: 1px;
2025-09-23 16:43:57 +00:00
}
.taskbar-item {
background-color: var(--taskbar-item-background);
2025-09-24 09:45:36 +00:00
color: inherit;
2025-09-23 16:43:57 +00:00
border: 1px solid var(--taskbar-item-border-color);
2025-09-24 09:45:36 +00:00
border-radius: 4px;
padding: 2px 6px;
font-size: 12px;
2025-09-23 16:43:57 +00:00
cursor: pointer;
transition: background-color 0.2s ease;
2025-09-24 09:45:36 +00:00
white-space: nowrap;
2025-09-23 16:43:57 +00:00
}
.taskbar-item:hover {
background-color: var(--taskbar-item-background-hover);
}
.taskbar-item.is-active {
background-color: var(--taskbar-item-background-active);
font-weight: bold;
}
2025-09-24 09:45:36 +00:00
.taskbar-right-controls {
margin-left: auto;
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0; /* Prevent right controls from shrinking */
}
2025-09-25 02:56:23 +00:00
.language-switcher-wrapper {
2025-09-24 09:45:36 +00:00
position: relative;
}
2025-09-25 02:56:23 +00:00
.language-switcher {
2025-09-24 09:45:36 +00:00
background: none;
border: none;
color: inherit;
font-size: 12px;
font-weight: 500;
cursor: pointer;
padding: 0 4px;
2025-09-25 02:56:23 +00:00
min-width: 20px;
text-align: center;
2025-09-24 09:45:36 +00:00
}
.datetime {
display: flex;
align-items: center;
gap: 8px;
padding: 0 4px;
}
2025-09-25 02:56:23 +00:00
.language-menu {
2025-09-24 09:45:36 +00:00
position: absolute;
top: calc(100% + 4px);
right: 0;
width: 160px;
background-color: var(--start-menu-background);
border: 1px solid var(--start-menu-border-color);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
padding: 4px;
z-index: 10001;
}
2025-09-25 02:56:23 +00:00
.language-menu ul {
2025-09-24 09:45:36 +00:00
list-style: none;
padding: 0;
margin: 0;
}
2025-09-25 02:56:23 +00:00
.language-menu li {
2025-09-24 09:45:36 +00:00
display: flex;
align-items: center;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.15s ease-in-out;
}
2025-09-25 02:56:23 +00:00
.language-menu li:hover {
2025-09-24 09:45:36 +00:00
background-color: var(--taskbar-item-background-hover);
}
.checkmark {
width: 16px;
text-align: center;
margin-right: 4px;
}
2025-09-23 16:43:57 +00:00
</style>