bugfix; web tabs

This commit is contained in:
2026-04-13 20:58:40 -04:00
parent 6c82dce6f6
commit 5021138da6
11 changed files with 634 additions and 565 deletions

View File

@@ -5,6 +5,7 @@ import SplitterPanel from 'primevue/splitterpanel'
import ChartView from './components/ChartView.vue'
import ChatPanel from './components/ChatPanel.vue'
import LoginScreen from './components/LoginScreen.vue'
import BottomTray from './components/BottomTray.vue'
import { useChartStore } from './stores/chart'
import { useShapeStore } from './stores/shapes'
import { useIndicatorStore } from './stores/indicators'
@@ -137,14 +138,19 @@ onBeforeUnmount(() => {
:error-message="authError"
@authenticate="handleAuthenticate"
/>
<Splitter v-else-if="!isMobile" class="main-splitter">
<SplitterPanel :size="62" :minSize="40" class="chart-panel">
<ChartView />
</SplitterPanel>
<SplitterPanel :size="38" :minSize="20" class="chat-panel">
<ChatPanel />
</SplitterPanel>
</Splitter>
<div v-else-if="!isMobile" class="desktop-layout">
<div class="top-area">
<Splitter class="main-splitter">
<SplitterPanel :size="62" :minSize="40" class="chart-panel">
<ChartView />
</SplitterPanel>
<SplitterPanel :size="38" :minSize="20" class="chat-panel">
<ChatPanel />
</SplitterPanel>
</Splitter>
</div>
<BottomTray />
</div>
<div v-else class="mobile-layout">
<ChatPanel />
</div>
@@ -165,8 +171,21 @@ onBeforeUnmount(() => {
background: #0f0f0f !important;
}
.desktop-layout {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.top-area {
flex: 1;
min-height: 0;
overflow: hidden;
}
.main-splitter {
height: 100vh !important;
height: 100% !important;
background: #0f0f0f !important;
}
@@ -200,7 +219,7 @@ onBeforeUnmount(() => {
right: 0;
bottom: 0;
z-index: 9999;
cursor: col-resize;
cursor: auto;
background: transparent;
}

View File

@@ -0,0 +1,279 @@
<script setup lang="ts">
import { ref, computed, onBeforeUnmount, 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'
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: MouseEvent) {
e.preventDefault()
resizeStartY = e.clientY
resizeStartHeight = expandedHeight.value
document.addEventListener('mousemove', onResizeMove)
document.addEventListener('mouseup', stopResize)
}
function onResizeMove(e: MouseEvent) {
const delta = resizeStartY - e.clientY // dragging up increases height
expandedHeight.value = Math.max(MIN_EXPANDED, resizeStartHeight + delta)
}
function stopResize() {
document.removeEventListener('mousemove', onResizeMove)
document.removeEventListener('mouseup', stopResize)
}
onBeforeUnmount(() => {
document.removeEventListener('mousemove', onResizeMove)
document.removeEventListener('mouseup', stopResize)
})
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" @mousedown="startResize" />
<Tabs :value="activeTab" class="tray-tabs">
<TabList class="tray-tab-list">
<Tab value="orders" @click="onTabClick('orders')">Orders</Tab>
<Tab value="strategies" @click="onTabClick('strategies')">Strategies</Tab>
<Tab value="positions" @click="onTabClick('positions')">Positions</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"></button>
</TabList>
<TabPanels v-if="isExpanded" class="tray-panels">
<TabPanel value="orders" class="tray-panel"><OrdersTab /></TabPanel>
<TabPanel value="strategies" class="tray-panel"><PlaceholderTab label="Strategies" /></TabPanel>
<TabPanel value="positions" class="tray-panel"><PlaceholderTab label="Positions" /></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>

View File

@@ -0,0 +1,69 @@
<script setup lang="ts">
import DataTable from 'primevue/datatable'
import Column from 'primevue/column'
import { useOrderStore } from '../../stores/orders'
import { storeToRefs } from 'pinia'
const ordersStore = useOrderStore()
const { orders } = storeToRefs(ordersStore)
</script>
<template>
<DataTable
:value="orders"
scrollable
scrollHeight="flex"
size="small"
class="orders-table"
:empty-message="'No orders'"
>
<Column field="tokenIn" header="Token In" style="min-width: 90px" />
<Column field="tokenOut" header="Token Out" style="min-width: 90px" />
<Column field="route.exchange" header="Exchange" style="min-width: 100px" />
<Column field="route.fee" header="Fee" style="min-width: 70px" />
<Column field="amount" header="Amount" style="min-width: 100px" />
<Column field="minFillAmount" header="Min Fill" style="min-width: 100px" />
<Column field="amountIsInput" header="Amt Is Input" style="min-width: 100px">
<template #body="{ data }">{{ data.amountIsInput ? 'Yes' : 'No' }}</template>
</Column>
<Column field="conditionalOrder" header="Condition" style="min-width: 100px" />
</DataTable>
</template>
<style scoped>
.orders-table {
flex: 1;
width: 100%;
height: 100%;
font-size: 12px;
}
.orders-table :deep(.p-datatable-header-cell) {
background: #1a1a1a !important;
color: #aaa !important;
border-color: #2e2e2e !important;
padding: 4px 8px !important;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.orders-table :deep(.p-datatable-row-cell) {
background: #0f0f0f !important;
color: #dbdbdb !important;
border-color: #1e1e1e !important;
padding: 3px 8px !important;
}
.orders-table :deep(tr:hover .p-datatable-row-cell) {
background: #1a1a1a !important;
}
.orders-table :deep(.p-datatable-empty-message td) {
background: #0f0f0f !important;
color: #555 !important;
text-align: center;
padding: 16px !important;
}
</style>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
defineProps<{ label: string }>()
</script>
<template>
<div class="placeholder-tab">
<span>{{ label }} no data</span>
</div>
</template>
<style scoped>
.placeholder-tab {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: #666;
font-size: 13px;
}
</style>