Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/public/css/layout.css
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
64 changes: 61 additions & 3 deletions app/public/css/menu.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.sidebar-container {
width: 0;
min-width: 0;
max-width: 50dvw;
overflow: hidden;
padding: 20px 0;
display: flex;
Expand All @@ -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;
Expand Down Expand Up @@ -164,7 +222,7 @@
#schema-controls {
position: absolute;
padding: 10px;
bottom: 15px;
bottom: 0px;
left: 0px;
display: flex;
flex-direction: row;
Expand Down
2 changes: 2 additions & 0 deletions app/templates/chat.j2
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
<div id="container">
{% include 'components/left_toolbar.j2' %}
{% include 'components/sidebar_menu.j2' %}
<div class="resize-handle" id="menu-resize-handle" data-target="menu-container"></div>
{% include 'components/sidebar_schema.j2' %}
<div class="resize-handle" id="schema-resize-handle" data-target="schema-container"></div>

<div id="chat-container" class="chat-container">

Expand Down
2 changes: 2 additions & 0 deletions app/ts/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
setupToolbar,
handleWindowResize,
setupCustomDropdown,
setupResizeHandles,
} from "./modules/ui";
import { setupAuthenticationModal, setupDatabaseModal } from "./modules/modals";
import { resizeGraph, showGraph } from "./modules/schema";
Expand Down Expand Up @@ -226,6 +227,7 @@ function setupUIComponents() {
initLeftToolbar();
setupCustomDropdown();
setupTextareaAutoResize();
setupResizeHandles();
}

function loadInitialData() {
Expand Down
128 changes: 128 additions & 0 deletions app/ts/modules/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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%";
Expand All @@ -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%";
Expand Down Expand Up @@ -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);
});
}