bugfix
This commit is contained in:
157
web/src/App.vue
157
web/src/App.vue
@@ -1,7 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue'
|
||||
import Splitter from 'primevue/splitter'
|
||||
import SplitterPanel from 'primevue/splitterpanel'
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import ChartView from './components/ChartView.vue'
|
||||
import ChatPanel from './components/ChatPanel.vue'
|
||||
import LoginScreen from './components/LoginScreen.vue'
|
||||
@@ -17,10 +15,35 @@ import { authService } from './composables/useAuth'
|
||||
|
||||
const isAuthenticated = authService.isAuthenticated
|
||||
const authError = ref<string>()
|
||||
const isDragging = ref(false)
|
||||
const isMobile = ref(false)
|
||||
let stateSyncCleanup: (() => void) | null = null
|
||||
|
||||
// Horizontal split: chart width in pixels (initialized on mount)
|
||||
const CHART_MIN_PX = 300
|
||||
const CHAT_MIN_PX = 240
|
||||
const CHART_DEFAULT_RATIO = 0.62
|
||||
const chartWidth = ref(0)
|
||||
let hDragStartX = 0
|
||||
let hDragStartWidth = 0
|
||||
|
||||
function initChartWidth() {
|
||||
chartWidth.value = Math.round(window.innerWidth * CHART_DEFAULT_RATIO)
|
||||
}
|
||||
|
||||
function startHDrag(e: PointerEvent) {
|
||||
e.preventDefault()
|
||||
;(e.target as HTMLElement).setPointerCapture(e.pointerId)
|
||||
hDragStartX = e.clientX
|
||||
hDragStartWidth = chartWidth.value
|
||||
}
|
||||
|
||||
function onHDragMove(e: PointerEvent) {
|
||||
if (!e.buttons) return
|
||||
const delta = e.clientX - hDragStartX
|
||||
const maxWidth = window.innerWidth - CHAT_MIN_PX - 4
|
||||
chartWidth.value = Math.max(CHART_MIN_PX, Math.min(maxWidth, hDragStartWidth + delta))
|
||||
}
|
||||
|
||||
// Check screen width for mobile layout
|
||||
const checkMobile = () => {
|
||||
isMobile.value = window.innerWidth < 768
|
||||
@@ -31,7 +54,6 @@ 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.symbol = null as any
|
||||
chartStore.start_time = null
|
||||
chartStore.end_time = null
|
||||
@@ -41,13 +63,15 @@ watch(isMobile, (mobile) => {
|
||||
|
||||
// Check if user is already authenticated on page load
|
||||
onMounted(async () => {
|
||||
// Try to restore session from stored token
|
||||
initChartWidth()
|
||||
checkMobile()
|
||||
window.addEventListener('resize', checkMobile)
|
||||
|
||||
const token = authService.getToken()
|
||||
if (token) {
|
||||
const sessionValid = await authService.checkAuth()
|
||||
if (sessionValid) {
|
||||
isAuthenticated.value = true
|
||||
// Connect WebSocket with existing token
|
||||
try {
|
||||
await wsManager.connect(token)
|
||||
await initializeApp()
|
||||
@@ -56,33 +80,21 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize mobile check
|
||||
checkMobile()
|
||||
window.addEventListener('resize', checkMobile)
|
||||
})
|
||||
|
||||
const handleAuthenticate = async (email: string, password: string) => {
|
||||
authError.value = undefined
|
||||
|
||||
try {
|
||||
// Step 1: Login via HTTP to get JWT token
|
||||
const result = await authService.login(email, password)
|
||||
|
||||
if (!result.success) {
|
||||
authError.value = result.error || 'Login failed'
|
||||
return
|
||||
}
|
||||
|
||||
if (!result.token) {
|
||||
authError.value = 'No token received from server'
|
||||
return
|
||||
}
|
||||
|
||||
// Step 2: Connect WebSocket with JWT token for real-time sync
|
||||
await wsManager.connect(result.token)
|
||||
|
||||
// Step 3: Initialize application
|
||||
await initializeApp()
|
||||
} catch (err: any) {
|
||||
authError.value = err.message || 'Authentication failed'
|
||||
@@ -91,7 +103,6 @@ const handleAuthenticate = async (email: string, password: string) => {
|
||||
}
|
||||
|
||||
const initializeApp = async () => {
|
||||
// Initialize state sync after successful authentication
|
||||
const chartStore = useChartStore()
|
||||
const shapeStore = useShapeStore()
|
||||
const indicatorStore = useIndicatorStore()
|
||||
@@ -107,26 +118,9 @@ const initializeApp = async () => {
|
||||
stateSyncCleanup = stateSync.cleanup
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
if (stateSyncCleanup) stateSyncCleanup()
|
||||
wsManager.disconnect()
|
||||
})
|
||||
</script>
|
||||
@@ -140,35 +134,28 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
<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 class="chart-panel" :style="{ width: chartWidth + 'px' }">
|
||||
<ChartView />
|
||||
</div>
|
||||
<div class="h-grabber" @pointerdown="startHDrag" @pointermove="onHDragMove" />
|
||||
<div class="chat-panel">
|
||||
<ChatPanel />
|
||||
</div>
|
||||
</div>
|
||||
<BottomTray />
|
||||
</div>
|
||||
<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;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: #0f0f0f !important;
|
||||
}
|
||||
|
||||
.app-container.dark {
|
||||
background: #0f0f0f !important;
|
||||
background: #0f0f0f;
|
||||
}
|
||||
|
||||
.desktop-layout {
|
||||
@@ -181,46 +168,38 @@ onBeforeUnmount(() => {
|
||||
.top-area {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-splitter {
|
||||
height: 100% !important;
|
||||
background: #0f0f0f !important;
|
||||
}
|
||||
|
||||
.main-splitter :deep(.p-splitter-gutter) {
|
||||
background: #2e2e2e !important;
|
||||
}
|
||||
|
||||
.main-splitter :deep(.p-splitter-gutter-handle) {
|
||||
background: #2e2e2e !important;
|
||||
}
|
||||
|
||||
.chart-panel,
|
||||
.chat-panel {
|
||||
height: 100% !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chart-panel :deep(.p-splitter-panel-content),
|
||||
.chat-panel :deep(.p-splitter-panel-content) {
|
||||
.chart-panel {
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.drag-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
cursor: auto;
|
||||
background: transparent;
|
||||
.h-grabber {
|
||||
width: 4px;
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
background: #2e2e2e;
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
.h-grabber:hover {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.chat-panel {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mobile-layout {
|
||||
@@ -230,10 +209,4 @@ onBeforeUnmount(() => {
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.main-splitter {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user