From 75912e5cfb7e7302c3b84b43c912f7f003bb59b3 Mon Sep 17 00:00:00 2001 From: Fedor Date: Tue, 11 Nov 2025 15:24:27 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D1=8C=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=88=D0=B8=D1=80=D0=B8=D0=BD=D1=8B=20AI=20Draw?= =?UTF-8?q?er?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен resize handle (полоска для перетаскивания) слева от drawer - Реализовано изменение ширины перетаскиванием (от 300px до 50% экрана) - Сохранение ширины в localStorage - Автоматическое обновление margin для main-container - Обработка изменения размера окна браузера - Скрытие resize handle на мобильных устройствах --- layouts/v7/resources/css/ai-drawer.css | 48 +++++++++ layouts/v7/resources/js/ai-drawer-simple.js | 102 ++++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/layouts/v7/resources/css/ai-drawer.css b/layouts/v7/resources/css/ai-drawer.css index 6c896844..533666e9 100644 --- a/layouts/v7/resources/css/ai-drawer.css +++ b/layouts/v7/resources/css/ai-drawer.css @@ -4,6 +4,8 @@ right: -400px; /* Начально скрыт */ top: 0; width: 400px; + min-width: 300px; /* Минимальная ширина */ + max-width: 50vw; /* Максимальная ширина - половина экрана */ height: 100vh; background: #ffffff; /* Чистый белый фон */ box-shadow: -2px 0 15px rgba(0,0,0,0.1); @@ -15,6 +17,38 @@ border-left: 1px solid #e9ecef; } +/* Полоска для изменения ширины */ +.ai-drawer-resize-handle { + position: absolute; + left: 0; + top: 0; + width: 4px; + height: 100%; + cursor: ew-resize; + background: transparent; + z-index: 1000000; + transition: background 0.2s ease; +} + +.ai-drawer-resize-handle:hover { + background: #007bff; +} + +.ai-drawer-resize-handle:active { + background: #0056b3; +} + +/* Визуальная индикация при перетаскивании */ +.ai-drawer.resizing { + transition: none !important; + user-select: none; +} + +.ai-drawer.resizing .ai-drawer-resize-handle { + background: #007bff; + width: 4px; +} + .ai-drawer.open { right: 0; } @@ -192,6 +226,11 @@ body.ai-drawer-open .ai-drawer-toggle { margin-right: 400px; } +/* Динамический margin для main-container при изменении ширины drawer */ +.main-container.drawer-open[data-drawer-width] { + margin-right: var(--drawer-width, 400px); +} + /* Плавающий индикатор загрузки */ .ai-loading-overlay { position: fixed; @@ -476,6 +515,13 @@ body.ai-drawer-open .ai-drawer-toggle { height: 100dvh; /* Динамическая высота viewport для мобильных */ display: flex; flex-direction: column; + min-width: 100%; + max-width: 100%; + } + + /* Скрываем resize handle на мобильных */ + .ai-drawer-resize-handle { + display: none; } .main-container.drawer-open { @@ -683,6 +729,8 @@ body.ai-drawer-open .ai-drawer-toggle { /* Десктопная версия drawer */ .ai-drawer { width: 400px; + min-width: 300px; + max-width: 50vw; right: -400px; height: 100vh; display: flex; diff --git a/layouts/v7/resources/js/ai-drawer-simple.js b/layouts/v7/resources/js/ai-drawer-simple.js index 7c559d6d..612f9bf7 100644 --- a/layouts/v7/resources/js/ai-drawer-simple.js +++ b/layouts/v7/resources/js/ai-drawer-simple.js @@ -5,6 +5,8 @@ class AIDrawer { this.avatarType = 'default'; this.sessionId = null; this.currentEventSource = null; // Для SSE соединения + this.drawerWidth = 400; // Текущая ширина drawer + this.isResizing = false; // Флаг перетаскивания this.init(); // Загружаем историю сразу при инициализации (при загрузке страницы) @@ -21,6 +23,7 @@ class AIDrawer { const drawerHTML = '' + '
' + + '
' + '
' + 'AI Ассистент' + '' + @@ -73,6 +76,7 @@ class AIDrawer { this.avatarButtons = document.querySelectorAll('.avatar-btn'); this.chatInput = document.querySelector('#ai-chat-input'); this.sendButton = document.querySelector('#ai-send-button'); + this.resizeHandle = document.querySelector('.ai-drawer-resize-handle'); // Обработчики событий if (this.toggleBtn) { @@ -119,8 +123,91 @@ class AIDrawer { // Восстанавливаем настройки this.restoreSettings(); + // Инициализируем изменение размера + this.initResize(); + + // Обработчик изменения размера окна - ограничиваем ширину если нужно + window.addEventListener('resize', () => { + if (this.drawerWidth > window.innerWidth / 2) { + const maxWidth = window.innerWidth / 2; + this.setDrawerWidth(maxWidth); + } + }); + console.log('AI Drawer: Простая инициализация завершена'); } + + initResize() { + if (!this.resizeHandle || !this.drawer) return; + + // Загружаем сохраненную ширину + const savedWidth = localStorage.getItem('ai-drawer-width'); + if (savedWidth) { + const width = parseInt(savedWidth, 10); + if (width >= 300 && width <= window.innerWidth / 2) { + this.setDrawerWidth(width); + } + } + + // Обработчики для перетаскивания + this.resizeHandle.addEventListener('mousedown', (e) => { + e.preventDefault(); + this.startResize(e); + }); + } + + startResize(e) { + this.isResizing = true; + this.drawer.classList.add('resizing'); + document.body.style.cursor = 'ew-resize'; + document.body.style.userSelect = 'none'; + + const startX = e.clientX; + const startWidth = this.drawerWidth; + + const doResize = (e) => { + if (!this.isResizing) return; + + const diff = startX - e.clientX; // Инвертируем, т.к. drawer справа + const newWidth = Math.max(300, Math.min(window.innerWidth / 2, startWidth + diff)); + + this.setDrawerWidth(newWidth); + }; + + const stopResize = () => { + this.isResizing = false; + this.drawer.classList.remove('resizing'); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + document.removeEventListener('mousemove', doResize); + document.removeEventListener('mouseup', stopResize); + + // Сохраняем ширину + localStorage.setItem('ai-drawer-width', this.drawerWidth.toString()); + }; + + document.addEventListener('mousemove', doResize); + document.addEventListener('mouseup', stopResize); + } + + setDrawerWidth(width) { + if (this.drawer) { + // Ограничиваем ширину максимум до половины экрана и минимум 300px + const maxWidth = window.innerWidth / 2; + const finalWidth = Math.max(300, Math.min(maxWidth, width)); + this.drawerWidth = finalWidth; + + this.drawer.style.width = finalWidth + 'px'; + + // Обновляем margin для main-container + const mainContainer = document.querySelector('.main-container'); + if (mainContainer && this.isOpen) { + mainContainer.style.setProperty('--drawer-width', finalWidth + 'px'); + mainContainer.style.marginRight = finalWidth + 'px'; + mainContainer.setAttribute('data-drawer-width', finalWidth); + } + } + } toggle() { if (this.isOpen) { @@ -139,6 +226,14 @@ class AIDrawer { document.body.classList.add('ai-drawer-open'); this.isOpen = true; + // Обновляем margin для main-container с текущей шириной + const mainContainer = document.querySelector('.main-container'); + if (mainContainer) { + mainContainer.style.setProperty('--drawer-width', this.drawerWidth + 'px'); + mainContainer.style.marginRight = this.drawerWidth + 'px'; + mainContainer.setAttribute('data-drawer-width', this.drawerWidth); + } + // История уже загружена при инициализации страницы // Не нужно дополнительных запросов при открытии } @@ -151,6 +246,13 @@ class AIDrawer { document.body.classList.remove('ai-drawer-open'); this.isOpen = false; + + // Убираем margin у main-container + const mainContainer = document.querySelector('.main-container'); + if (mainContainer) { + mainContainer.style.marginRight = ''; + mainContainer.removeAttribute('data-drawer-width'); + } } setFontSize(size) {