201 lines
5.0 KiB
Vue
201 lines
5.0 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue'
|
|
import Splitter from 'primevue/splitter'
|
|
import SplitterPanel from 'primevue/splitterpanel'
|
|
import ChartView from './components/ChartView.vue'
|
|
import ChatPanel from './components/ChatPanel.vue'
|
|
import LoginScreen from './components/LoginScreen.vue'
|
|
import { useOrderStore } from './stores/orders'
|
|
import { useChartStore } from './stores/chart'
|
|
import { useShapeStore } from './stores/shapes'
|
|
import { useStateSync } from './composables/useStateSync'
|
|
import { wsManager } from './composables/useWebSocket'
|
|
|
|
const isAuthenticated = ref(false)
|
|
const needsConfirmation = ref(false)
|
|
const authError = ref<string>()
|
|
const isDragging = ref(false)
|
|
const isMobile = ref(false)
|
|
let stateSyncCleanup: (() => void) | null = null
|
|
|
|
// Check screen width for mobile layout
|
|
const checkMobile = () => {
|
|
isMobile.value = window.innerWidth < 768
|
|
}
|
|
|
|
const chartStore = useChartStore()
|
|
|
|
// Watch mobile state and update ChartStore
|
|
watch(isMobile, (mobile) => {
|
|
if (mobile) {
|
|
// Set all chart state to null when chart is hidden
|
|
chartStore.chart_state.symbol = null as any
|
|
chartStore.chart_state.start_time = null
|
|
chartStore.chart_state.end_time = null
|
|
chartStore.chart_state.interval = null as any
|
|
}
|
|
})
|
|
|
|
// Check if we need password confirmation on first load
|
|
onMounted(async () => {
|
|
// Check if secrets store is initialized by trying to fetch a status endpoint
|
|
// For now, just default to false (user will see login screen)
|
|
needsConfirmation.value = false
|
|
|
|
// Initialize mobile check
|
|
checkMobile()
|
|
window.addEventListener('resize', checkMobile)
|
|
})
|
|
|
|
const handleAuthenticate = async (
|
|
password: string,
|
|
confirmPassword?: string,
|
|
newPassword?: string,
|
|
confirmNewPassword?: string
|
|
) => {
|
|
authError.value = undefined
|
|
|
|
try {
|
|
const result = await wsManager.connect(password, confirmPassword, newPassword, confirmNewPassword)
|
|
|
|
if (result.success) {
|
|
isAuthenticated.value = true
|
|
|
|
// Initialize state sync after successful authentication
|
|
const orderStore = useOrderStore()
|
|
const chartStore = useChartStore()
|
|
const shapeStore = useShapeStore()
|
|
const stateSync = useStateSync({
|
|
OrderStore: orderStore,
|
|
ChartStore: chartStore,
|
|
ShapeStore: shapeStore
|
|
})
|
|
stateSyncCleanup = stateSync.cleanup
|
|
} else {
|
|
authError.value = result.message
|
|
|
|
// If server says we need confirmation, update the flag
|
|
if (result.needsConfirmation) {
|
|
needsConfirmation.value = true
|
|
}
|
|
}
|
|
} catch (err) {
|
|
authError.value = 'Connection failed'
|
|
console.error('Authentication error:', err)
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
// Listen for splitter drag events
|
|
document.addEventListener('mousedown', (e) => {
|
|
// Check if the mousedown is on a splitter gutter
|
|
const target = e.target as HTMLElement
|
|
if (target.closest('.p-splitter-gutter')) {
|
|
isDragging.value = true
|
|
}
|
|
})
|
|
|
|
document.addEventListener('mouseup', () => {
|
|
isDragging.value = false
|
|
})
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('resize', checkMobile)
|
|
if (stateSyncCleanup) {
|
|
stateSyncCleanup()
|
|
}
|
|
wsManager.disconnect()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="app-container dark">
|
|
<LoginScreen
|
|
v-if="!isAuthenticated"
|
|
:needs-confirmation="needsConfirmation"
|
|
: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 class="mobile-layout">
|
|
<ChatPanel />
|
|
</div>
|
|
<!-- Transparent overlay to prevent iframe from capturing mouse events during drag -->
|
|
<div v-if="isDragging" class="drag-overlay"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.app-container {
|
|
width: 100vw !important;
|
|
height: 100vh !important;
|
|
overflow: hidden;
|
|
background: var(--p-surface-0) !important;
|
|
}
|
|
|
|
.app-container.dark {
|
|
background: var(--p-surface-0) !important;
|
|
}
|
|
|
|
.main-splitter {
|
|
height: 100vh !important;
|
|
background: var(--p-surface-0) !important;
|
|
}
|
|
|
|
.main-splitter :deep(.p-splitter-gutter) {
|
|
background: var(--p-surface-0) !important;
|
|
}
|
|
|
|
.main-splitter :deep(.p-splitter-gutter-handle) {
|
|
background: var(--p-surface-400) !important;
|
|
}
|
|
|
|
.chart-panel,
|
|
.chat-panel {
|
|
height: 100% !important;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.chart-panel :deep(.p-splitter-panel-content),
|
|
.chat-panel :deep(.p-splitter-panel-content) {
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.drag-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
z-index: 9999;
|
|
cursor: col-resize;
|
|
background: transparent;
|
|
}
|
|
|
|
.mobile-layout {
|
|
width: 100%;
|
|
height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
@media (max-width: 767px) {
|
|
.main-splitter {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|