Files
ai/web/src/components/BottomTray.vue

279 lines
6.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { ref, computed, type Component } from 'vue'
import Tabs from 'primevue/tabs'
import TabList from 'primevue/tablist'
import Tab from 'primevue/tab'
import TabPanels from 'primevue/tabpanels'
import TabPanel from 'primevue/tabpanel'
import OrdersTab from './tabs/OrdersTab.vue'
import PlaceholderTab from './tabs/PlaceholderTab.vue'
import ResearchTab from './tabs/ResearchTab.vue'
import StrategiesTab from './tabs/StrategiesTab.vue'
import IndicatorsTab from './tabs/IndicatorsTab.vue'
interface TempTab {
id: string
label: string
component: Component
props?: Record<string, any>
}
const COLLAPSED_HEIGHT = 34
const DEFAULT_EXPANDED = 260
const MIN_EXPANDED = 80
const isExpanded = ref(false)
const expandedHeight = ref(DEFAULT_EXPANDED)
const activeTab = ref('orders')
const tempTabs = ref<TempTab[]>([])
const trayStyle = computed(() => ({
height: isExpanded.value ? `${expandedHeight.value}px` : `${COLLAPSED_HEIGHT}px`,
}))
function onTabClick(tabId: string) {
if (!isExpanded.value) {
activeTab.value = tabId
isExpanded.value = true
} else if (activeTab.value === tabId) {
isExpanded.value = false
} else {
activeTab.value = tabId
}
}
function closeTab(tabId: string) {
const idx = tempTabs.value.findIndex(t => t.id === tabId)
if (idx === -1) return
tempTabs.value.splice(idx, 1)
if (activeTab.value === tabId) {
activeTab.value = tempTabs.value[idx - 1]?.id ?? tempTabs.value[0]?.id ?? 'orders'
if (tempTabs.value.length === 0) isExpanded.value = false
}
}
// Resize handle drag
let resizeStartY = 0
let resizeStartHeight = 0
function startResize(e: PointerEvent) {
e.preventDefault()
;(e.target as HTMLElement).setPointerCapture(e.pointerId)
resizeStartY = e.clientY
resizeStartHeight = expandedHeight.value
}
function onResizeMove(e: PointerEvent) {
if (!e.buttons) return
const delta = resizeStartY - e.clientY // dragging up increases height
expandedHeight.value = Math.max(MIN_EXPANDED, resizeStartHeight + delta)
}
defineExpose({
openTab(id: string, label: string, component: Component, props?: Record<string, any>) {
const existing = tempTabs.value.find(t => t.id === id)
if (!existing) {
tempTabs.value.push({ id, label, component, props })
}
activeTab.value = id
isExpanded.value = true
}
})
</script>
<template>
<div class="bottom-tray" :style="trayStyle">
<div v-if="isExpanded" class="tray-resize-handle" @pointerdown="startResize" @pointermove="onResizeMove" />
<Tabs :value="isExpanded ? activeTab : null" class="tray-tabs">
<TabList class="tray-tab-list">
<Tab value="positions" @click="onTabClick('positions')">Positions</Tab>
<Tab value="orders" @click="onTabClick('orders')">Orders</Tab>
<Tab value="indicators" @click="onTabClick('indicators')">Indicators</Tab>
<Tab value="research" @click="onTabClick('research')">Research</Tab>
<Tab value="strategies" @click="onTabClick('strategies')">Strategies</Tab>
<Tab
v-for="tab in tempTabs"
:key="tab.id"
:value="tab.id"
class="tab-closeable"
@click="onTabClick(tab.id)"
>
{{ tab.label }}
<button class="tab-close-btn" @click.stop="closeTab(tab.id)">×</button>
</Tab>
<div class="tray-spacer" />
<button v-if="isExpanded" class="tray-close-btn" @click="isExpanded = false" title="Minimize">
<i class="pi pi-chevron-down" />
</button>
</TabList>
<TabPanels v-if="isExpanded" class="tray-panels">
<TabPanel value="positions" class="tray-panel"><PlaceholderTab label="Positions" /></TabPanel>
<TabPanel value="orders" class="tray-panel"><OrdersTab /></TabPanel>
<TabPanel value="research" class="tray-panel"><ResearchTab /></TabPanel>
<TabPanel value="indicators" class="tray-panel"><IndicatorsTab /></TabPanel>
<TabPanel value="strategies" class="tray-panel"><StrategiesTab /></TabPanel>
<TabPanel
v-for="tab in tempTabs"
:key="tab.id"
:value="tab.id"
class="tray-panel"
>
<component :is="tab.component" v-bind="tab.props" />
</TabPanel>
</TabPanels>
</Tabs>
</div>
</template>
<style scoped>
.bottom-tray {
flex-shrink: 0;
width: 100%;
background: #0f0f0f;
border-top: 1px solid #2e2e2e;
overflow: hidden;
display: flex;
flex-direction: column;
transition: height 0.15s ease;
}
.tray-resize-handle {
height: 4px;
flex-shrink: 0;
background: #2e2e2e;
cursor: row-resize;
width: 100%;
}
.tray-resize-handle:hover {
background: #444;
}
.tray-tabs {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
.tray-tab-list {
height: 34px !important;
min-height: 34px !important;
flex-shrink: 0;
display: flex !important;
align-items: center;
padding: 0 4px;
background: #141414 !important;
border-bottom: 1px solid #2e2e2e !important;
gap: 2px;
}
/* Override PrimeVue Tab default styles */
.tray-tab-list :deep(.p-tab) {
padding: 0 12px !important;
height: 28px !important;
line-height: 28px !important;
font-size: 12px !important;
color: #888 !important;
background: transparent !important;
border: none !important;
border-radius: 4px !important;
cursor: pointer;
white-space: nowrap;
display: flex;
align-items: center;
gap: 6px;
}
.tray-tab-list :deep(.p-tab:hover) {
color: #dbdbdb !important;
background: #1e1e1e !important;
}
.tray-tab-list :deep(.p-tab-active) {
color: #dbdbdb !important;
background: #1e1e1e !important;
}
/* Hide the PrimeVue active indicator bar */
.tray-tab-list :deep(.p-tablist-active-bar) {
display: none !important;
}
.tab-close-btn {
background: none;
border: none;
color: #666;
cursor: pointer;
font-size: 13px;
line-height: 1;
padding: 0 2px;
display: flex;
align-items: center;
}
.tab-close-btn:hover {
color: #dbdbdb;
}
.tray-spacer {
flex: 1;
}
.tray-close-btn {
background: none;
border: none;
color: #666;
cursor: pointer;
font-size: 13px;
padding: 0 8px;
height: 28px;
display: flex;
align-items: center;
border-radius: 4px;
flex-shrink: 0;
}
.tray-close-btn:hover {
color: #dbdbdb;
background: #1e1e1e;
}
.tray-panels {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
min-height: 0;
padding: 0 !important;
background: #0f0f0f !important;
}
.tray-panels :deep(.p-tabpanels) {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
padding: 0 !important;
background: #0f0f0f !important;
}
.tray-panel {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 0 !important;
}
.tray-panel :deep(.p-tabpanel-content) {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 0 !important;
background: #0f0f0f !important;
}
</style>