164 lines
5.2 KiB
TypeScript
164 lines
5.2 KiB
TypeScript
import React, { useCallback, useRef, useState } from 'react';
|
|
import { Layout } from 'antd';
|
|
import { TopNav } from '../Nav/TopNav';
|
|
import { ConnectionTree } from '../Sidebar/ConnectionTree';
|
|
import { ConnectionProperties } from '../Sidebar/ConnectionProperties';
|
|
import { SessionTabs } from '../Tabs/SessionTabs';
|
|
|
|
const { Content } = Layout;
|
|
|
|
const MIN_SIDEBAR = 180;
|
|
const MAX_SIDEBAR = 600;
|
|
const DEFAULT_SIDEBAR = 260;
|
|
|
|
const MIN_PROPERTIES_HEIGHT = 150;
|
|
const DEFAULT_TREE_FRACTION = 0.6;
|
|
|
|
export const MainLayout: React.FC = () => {
|
|
const [sidebarWidth, setSidebarWidth] = useState(DEFAULT_SIDEBAR);
|
|
const [treeFraction, setTreeFraction] = useState(DEFAULT_TREE_FRACTION);
|
|
const isResizing = useRef(false);
|
|
const isVResizing = useRef(false);
|
|
const sidebarRef = useRef<HTMLDivElement>(null);
|
|
|
|
// Horizontal sidebar resize
|
|
const onMouseDown = useCallback((e: React.MouseEvent) => {
|
|
e.preventDefault();
|
|
isResizing.current = true;
|
|
document.body.style.cursor = 'col-resize';
|
|
document.body.style.userSelect = 'none';
|
|
|
|
const onMouseMove = (me: MouseEvent) => {
|
|
if (!isResizing.current) return;
|
|
const newWidth = Math.min(MAX_SIDEBAR, Math.max(MIN_SIDEBAR, me.clientX));
|
|
setSidebarWidth(newWidth);
|
|
};
|
|
|
|
const onMouseUp = () => {
|
|
isResizing.current = false;
|
|
document.body.style.cursor = '';
|
|
document.body.style.userSelect = '';
|
|
document.removeEventListener('mousemove', onMouseMove);
|
|
document.removeEventListener('mouseup', onMouseUp);
|
|
};
|
|
|
|
document.addEventListener('mousemove', onMouseMove);
|
|
document.addEventListener('mouseup', onMouseUp);
|
|
}, []);
|
|
|
|
// Vertical splitter between tree and properties
|
|
const onVMouseDown = useCallback((e: React.MouseEvent) => {
|
|
e.preventDefault();
|
|
isVResizing.current = true;
|
|
document.body.style.cursor = 'row-resize';
|
|
document.body.style.userSelect = 'none';
|
|
|
|
const onMouseMove = (me: MouseEvent) => {
|
|
if (!isVResizing.current || !sidebarRef.current) return;
|
|
const rect = sidebarRef.current.getBoundingClientRect();
|
|
const totalHeight = rect.height;
|
|
const relativeY = me.clientY - rect.top;
|
|
const propertiesHeight = totalHeight - relativeY;
|
|
// Enforce min heights
|
|
if (propertiesHeight < MIN_PROPERTIES_HEIGHT || relativeY < MIN_PROPERTIES_HEIGHT) return;
|
|
setTreeFraction(relativeY / totalHeight);
|
|
};
|
|
|
|
const onMouseUp = () => {
|
|
isVResizing.current = false;
|
|
document.body.style.cursor = '';
|
|
document.body.style.userSelect = '';
|
|
document.removeEventListener('mousemove', onMouseMove);
|
|
document.removeEventListener('mouseup', onMouseUp);
|
|
};
|
|
|
|
document.addEventListener('mousemove', onMouseMove);
|
|
document.addEventListener('mouseup', onMouseUp);
|
|
}, []);
|
|
|
|
return (
|
|
<Layout style={{ height: '100vh', overflow: 'hidden' }}>
|
|
<TopNav />
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'row', flex: 1, overflow: 'hidden' }}>
|
|
{/* Resizable sidebar */}
|
|
<div
|
|
ref={sidebarRef}
|
|
style={{
|
|
width: sidebarWidth,
|
|
flexShrink: 0,
|
|
borderRight: '1px solid #f0f0f0',
|
|
overflow: 'hidden',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
background: '#fff',
|
|
}}
|
|
>
|
|
{/* Connection tree (top) */}
|
|
<div style={{ flex: `0 0 ${treeFraction * 100}%`, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
|
|
<ConnectionTree />
|
|
</div>
|
|
|
|
{/* Vertical splitter */}
|
|
<div
|
|
onMouseDown={onVMouseDown}
|
|
style={{
|
|
height: 4,
|
|
cursor: 'row-resize',
|
|
background: 'transparent',
|
|
flexShrink: 0,
|
|
zIndex: 10,
|
|
borderTop: '1px solid #f0f0f0',
|
|
transition: 'background 0.15s',
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
(e.currentTarget as HTMLDivElement).style.background = '#1677ff40';
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
if (!isVResizing.current)
|
|
(e.currentTarget as HTMLDivElement).style.background = 'transparent';
|
|
}}
|
|
/>
|
|
|
|
{/* Connection properties (bottom) */}
|
|
<div style={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
|
|
<ConnectionProperties />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Resize handle */}
|
|
<div
|
|
onMouseDown={onMouseDown}
|
|
style={{
|
|
width: 4,
|
|
cursor: 'col-resize',
|
|
background: 'transparent',
|
|
flexShrink: 0,
|
|
zIndex: 10,
|
|
transition: 'background 0.15s',
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
(e.currentTarget as HTMLDivElement).style.background = '#1677ff40';
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
if (!isResizing.current)
|
|
(e.currentTarget as HTMLDivElement).style.background = 'transparent';
|
|
}}
|
|
/>
|
|
|
|
{/* Main content area */}
|
|
<Content
|
|
style={{
|
|
flex: 1,
|
|
overflow: 'hidden',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
}}
|
|
>
|
|
<SessionTabs />
|
|
</Content>
|
|
</div>
|
|
</Layout>
|
|
);
|
|
};
|