Spaces:
Running
Running
| <template> | |
| <nav | |
| class="sidebar-menu" | |
| :class="{ expanded: isExpanded }" | |
| @mouseenter="isExpanded = true" | |
| @mouseleave="isExpanded = false" | |
| > | |
| <div | |
| class="logo-section" | |
| @click="handleLogoClick" | |
| title="Click to test WebSocket connection" | |
| > | |
| <img src="@/assets/logo.png" alt="Trigo" class="logo clickable" /> | |
| <h1 class="app-title">Trigo</h1> | |
| </div> | |
| <div class="menu-tabs"> | |
| <router-link | |
| v-for="tab in tabs" | |
| :key="tab.path" | |
| :to="tab.path" | |
| class="tab-button" | |
| :class="{ active: isActiveTab(tab.path) }" | |
| > | |
| <span class="tab-icon">{{ tab.icon }}</span> | |
| <span class="tab-label">{{ tab.label }}</span> | |
| </router-link> | |
| </div> | |
| <div class="sidebar-footer"> | |
| <p class="version">v1.0.0</p> | |
| </div> | |
| </nav> | |
| </template> | |
| <script setup lang="ts"> | |
| import { ref, computed } from "vue"; | |
| import { useRoute } from "vue-router"; | |
| import { useSocket } from "@/composables/useSocket"; | |
| interface Tab { | |
| path: string; | |
| label: string; | |
| icon: string; | |
| } | |
| const tabs: Tab[] = [ | |
| { path: "/", label: "Single Game", icon: "🎮" }, | |
| { path: "/vs-ai", label: "VS AI", icon: "🤖" }, | |
| { path: "/vs-people", label: "VS People", icon: "👥" }, | |
| { path: "/library", label: "Game Library", icon: "📚" } | |
| ]; | |
| const route = useRoute(); | |
| const isExpanded = ref(false); | |
| const isActiveTab = (path: string): boolean => { | |
| return route.path === path; | |
| }; | |
| // WebSocket echo test | |
| const { sendEcho, connected: socketConnected } = useSocket(); | |
| const handleLogoClick = async () => { | |
| try { | |
| console.log("[Echo Test] Sending echo request..."); | |
| console.log("[Echo Test] Socket connected:", socketConnected.value); | |
| const response = await sendEcho("Echo test from Trigo sidebar!"); | |
| console.log("[Echo Test] Response:", response); | |
| // Show alert with the response | |
| alert(`WebSocket Echo Test\n\n${response}`); | |
| } catch (error) { | |
| console.error("[Echo Test] Error:", error); | |
| alert( | |
| `WebSocket Echo Test Failed\n\n${error instanceof Error ? error.message : String(error)}` | |
| ); | |
| } | |
| }; | |
| </script> | |
| <style lang="scss" scoped> | |
| .sidebar-menu { | |
| width: 70px; // Collapsed width (icon only) | |
| background: #2a2a2a; | |
| display: flex; | |
| flex-direction: column; | |
| border-right: 1px solid #404040; | |
| padding: 20px 0; | |
| flex-shrink: 0; | |
| transition: width 0.3s ease; | |
| overflow: hidden; | |
| position: relative; | |
| &.expanded { | |
| width: 220px; // Expanded width (icon + text) | |
| } | |
| } | |
| .logo-section { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| padding: 0 10px 30px; | |
| border-bottom: 1px solid #404040; | |
| margin-bottom: 20px; | |
| min-height: 110px; // Prevent layout shift | |
| cursor: pointer; | |
| transition: background-color 0.2s ease; | |
| &:hover { | |
| background-color: rgba(255, 255, 255, 0.05); | |
| } | |
| &:active { | |
| background-color: rgba(255, 255, 255, 0.1); | |
| } | |
| } | |
| .logo { | |
| width: 50px; | |
| height: 50px; | |
| margin-bottom: 12px; | |
| border-radius: 8px; | |
| flex-shrink: 0; | |
| transition: | |
| transform 0.2s ease, | |
| opacity 0.2s ease; | |
| &.clickable { | |
| .logo-section:hover & { | |
| transform: scale(1.1); | |
| opacity: 0.9; | |
| } | |
| .logo-section:active & { | |
| transform: scale(0.95); | |
| } | |
| } | |
| } | |
| .app-title { | |
| font-size: 20px; | |
| font-weight: 600; | |
| color: #e0e0e0; | |
| margin: 0; | |
| white-space: nowrap; | |
| opacity: 0; | |
| transition: opacity 0.2s ease 0.1s; | |
| .expanded & { | |
| opacity: 1; | |
| } | |
| } | |
| .menu-tabs { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 4px; | |
| padding: 0 10px; | |
| } | |
| .tab-button { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| padding: 14px 12px; | |
| border-radius: 8px; | |
| background: transparent; | |
| color: #a0a0a0; | |
| text-decoration: none; | |
| font-size: 15px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| border-left: 3px solid transparent; | |
| position: relative; | |
| white-space: nowrap; | |
| &:hover { | |
| background: #353535; | |
| color: #e0e0e0; | |
| } | |
| &.active { | |
| background: #3a3a3a; | |
| color: #e94560; | |
| border-left-color: #e94560; | |
| .tab-icon { | |
| transform: scale(1.1); | |
| } | |
| } | |
| } | |
| .tab-icon { | |
| font-size: 22px; | |
| line-height: 1; | |
| transition: transform 0.2s ease; | |
| min-width: 28px; | |
| text-align: center; | |
| flex-shrink: 0; | |
| } | |
| .tab-label { | |
| flex: 1; | |
| opacity: 0; | |
| transition: opacity 0.2s ease 0.1s; | |
| .expanded & { | |
| opacity: 1; | |
| } | |
| } | |
| .sidebar-footer { | |
| padding: 20px 10px; | |
| border-top: 1px solid #404040; | |
| text-align: center; | |
| } | |
| .version { | |
| font-size: 11px; | |
| color: #606060; | |
| margin: 0; | |
| white-space: nowrap; | |
| opacity: 0; | |
| transition: opacity 0.2s ease 0.1s; | |
| .expanded & { | |
| opacity: 1; | |
| } | |
| } | |
| </style> | |