From cb213a3ec68f538e8c2a62eb6dd5fd1fa6346b25 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 3 Sep 2025 14:46:55 +0300 Subject: [PATCH] commit --- app/public/css/layout.css | 5 +- app/public/css/menu.css | 64 ++++++++++++++++++- app/templates/chat.j2 | 2 + app/ts/app.ts | 4 +- app/ts/modules/ui.ts | 128 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 197 insertions(+), 6 deletions(-) diff --git a/app/public/css/layout.css b/app/public/css/layout.css index 88dc45a0..21d1c2d7 100644 --- a/app/public/css/layout.css +++ b/app/public/css/layout.css @@ -1,11 +1,12 @@ /* Main Layout Components */ #container { - height: 98%; - width: 100%; + height: calc(100% - 40px); + width: calc(100% - 40px); display: flex; flex-direction: row; position: relative; + margin-left: 40px; } .logo { diff --git a/app/public/css/menu.css b/app/public/css/menu.css index 30ce1c76..e17de8cf 100644 --- a/app/public/css/menu.css +++ b/app/public/css/menu.css @@ -3,6 +3,7 @@ .sidebar-container { width: 0; min-width: 0; + max-width: 50dvw; overflow: hidden; padding: 20px 0; display: flex; @@ -17,13 +18,70 @@ } .sidebar-container.open { - width: 30dvw; + width: 50%; padding: 20px; - left: 48px; visibility: visible; pointer-events: auto; } +.sidebar-container.resizing { + transition: none; /* Disable transitions while resizing */ +} + +/* Resize handle styles */ +.resize-handle { + width: 6px; + background: transparent; + cursor: col-resize; + position: relative; + z-index: 10; + opacity: 0; + transition: opacity 0.2s ease, background-color 0.2s ease; + flex-shrink: 0; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.resize-handle::before { + content: ''; + width: 3px; + height: 40px; + background: var(--falkor-border-primary); + border-radius: 2px; + transition: background-color 0.2s ease; + opacity: 0.6; +} + +.resize-handle:hover::before, +.resize-handle.resizing::before { + background: var(--falkor-primary); + opacity: 1; +} + +.resize-handle:hover, +.resize-handle.resizing { + opacity: 1; +} + +/* Show resize handles only when sidebars are open */ +.sidebar-container.open + .resize-handle { + opacity: 0.7; +} + +.sidebar-container.open + .resize-handle:hover, +.sidebar-container.open + .resize-handle.resizing { + opacity: 1; +} + +/* Hide resize handles when sidebars are closed */ +.sidebar-container:not(.open) + .resize-handle { + opacity: 0; + pointer-events: none; + width: 0; +} + #menu-content { flex-grow: 1; display: flex; @@ -164,7 +222,7 @@ #schema-controls { position: absolute; padding: 10px; - bottom: 15px; + bottom: 0px; left: 0px; display: flex; flex-direction: row; diff --git a/app/templates/chat.j2 b/app/templates/chat.j2 index 637aa435..d39e76c5 100644 --- a/app/templates/chat.j2 +++ b/app/templates/chat.j2 @@ -10,7 +10,9 @@
{% include 'components/left_toolbar.j2' %} {% include 'components/sidebar_menu.j2' %} + {% include 'components/sidebar_schema.j2' %} +
diff --git a/app/ts/app.ts b/app/ts/app.ts index b7e5047f..8b6356fd 100644 --- a/app/ts/app.ts +++ b/app/ts/app.ts @@ -17,6 +17,7 @@ import { setupToolbar, handleWindowResize, setupCustomDropdown, + setupResizeHandles, } from "./modules/ui"; import { setupAuthenticationModal, setupDatabaseModal } from "./modules/modals"; import { resizeGraph, showGraph } from "./modules/schema"; @@ -225,7 +226,8 @@ function setupUIComponents() { // initialize left toolbar behavior (burger, responsive default) initLeftToolbar(); setupCustomDropdown(); - setupTextareaAutoResize() + setupTextareaAutoResize(); + setupResizeHandles(); } function loadInitialData() { diff --git a/app/ts/modules/ui.ts b/app/ts/modules/ui.ts index 28fdf4ff..a3bae915 100644 --- a/app/ts/modules/ui.ts +++ b/app/ts/modules/ui.ts @@ -12,11 +12,18 @@ export function toggleContainer(container: HTMLElement, onOpen?: () => void) { allContainers.forEach((c) => { if (c !== container && c.classList.contains("open")) { c.classList.remove("open"); + // Clear the inline width style when closing other panels + (c as HTMLElement).style.width = ''; } }); if (!container.classList.contains("open")) { container.classList.add("open"); + + // Reset to default 50% width when opening + if (!isMobile) { + container.style.width = '50%'; + } if (!isMobile && DOM.chatContainer) { DOM.chatContainer.style.paddingRight = "10%"; @@ -25,6 +32,9 @@ export function toggleContainer(container: HTMLElement, onOpen?: () => void) { if (onOpen) onOpen(); } else { container.classList.remove("open"); + + // Clear any inline width style that was set during resizing + container.style.width = ''; if (!isMobile && DOM.chatContainer) { DOM.chatContainer.style.paddingRight = "20%"; @@ -246,3 +256,121 @@ export function setupCustomDropdown() { } }); } + +export function setupResizeHandles() { + const resizeHandles = document.querySelectorAll('.resize-handle'); + + resizeHandles.forEach(handle => { + let isResizing = false; + let startX = 0; + let startWidth = 0; + let targetContainer: HTMLElement | null = null; + + const handleMouseDown = (e: MouseEvent | { clientX: number; preventDefault: () => void }) => { + isResizing = true; + startX = e.clientX; + + // Get the target container from data-target attribute + const targetId = (handle as HTMLElement).getAttribute('data-target'); + targetContainer = targetId ? document.getElementById(targetId) : null; + + if (targetContainer) { + startWidth = targetContainer.offsetWidth; + (handle as HTMLElement).classList.add('resizing'); + targetContainer.classList.add('resizing'); + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + } + + e.preventDefault(); + }; + + const handleMouseMove = (e: MouseEvent | { clientX: number }) => { + if (!isResizing || !targetContainer) return; + + const deltaX = e.clientX - startX; + const newWidth = startWidth + deltaX; + + // Get parent container width for percentage calculations + const parentWidth = targetContainer.parentElement?.offsetWidth || window.innerWidth; + const newWidthPercent = (newWidth / parentWidth) * 100; + + // Set minimum and maximum widths as percentages of parent + const collapseThreshold = 25; // 25% of parent width + const maxWidthPercent = 60; // 60% of parent width + + // If width goes below 25%, collapse the panel + if (newWidthPercent < collapseThreshold) { + // Directly close the container + const isMobile = window.innerWidth <= 768; + targetContainer.classList.remove('open'); + + // Clear the inline width style that was set during resizing + targetContainer.style.width = ''; + + // Reset chat container padding when closing + if (!isMobile && DOM.chatContainer) { + DOM.chatContainer.style.paddingRight = "20%"; + DOM.chatContainer.style.paddingLeft = "20%"; + } + + // Clean up resize state + isResizing = false; + (handle as HTMLElement).classList.remove('resizing'); + targetContainer.classList.remove('resizing'); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + targetContainer = null; + return; + } + + const clampedWidthPercent = Math.max(collapseThreshold, Math.min(maxWidthPercent, newWidthPercent)); + targetContainer.style.width = clampedWidthPercent + '%'; + + // Trigger graph resize if schema container is being resized + if (targetContainer.id === 'schema-container' && targetContainer.classList.contains('open')) { + setTimeout(() => { + resizeGraph(); + }, 50); + } + }; + + const handleMouseUp = () => { + if (isResizing) { + isResizing = false; + (handle as HTMLElement).classList.remove('resizing'); + if (targetContainer) { + targetContainer.classList.remove('resizing'); + } + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + targetContainer = null; + } + }; + + handle.addEventListener('mousedown', handleMouseDown as EventListener); + document.addEventListener('mousemove', handleMouseMove as EventListener); + document.addEventListener('mouseup', handleMouseUp); + + // Handle touch events for mobile + handle.addEventListener('touchstart', (e: Event) => { + const touchEvent = e as TouchEvent; + const touch = touchEvent.touches[0]; + handleMouseDown({ + clientX: touch.clientX, + preventDefault: () => e.preventDefault() + }); + }); + + document.addEventListener('touchmove', (e: Event) => { + if (isResizing) { + const touchEvent = e as TouchEvent; + const touch = touchEvent.touches[0]; + handleMouseMove({ clientX: touch.clientX }); + e.preventDefault(); + } + }); + + document.addEventListener('touchend', handleMouseUp); + }); +}