From a4a4ee795f8ddd50cf7ae6542c439d7696afadd4 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:41:18 +0000 Subject: [PATCH 1/2] feat: Add collapse/expand functionality to file list panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added toggle button (◀) in file list header for collapsing/expanding - Implemented smooth slide animation using CSS transforms - Added minimized restore button (▶) on left side when collapsed - State persistence using localStorage (fileListCollapsed) - Full dark/light theme support with styled buttons - Responsive design - hidden on mobile devices - Follows same pattern as existing output panel toggle Resolves #70 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: 小小高 --- SharpPad/wwwroot/fileSystem/fileManager.js | 43 +++++++++++ SharpPad/wwwroot/index.html | 6 ++ SharpPad/wwwroot/styles/fileList.css | 89 +++++++++++++++++++++- 3 files changed, 136 insertions(+), 2 deletions(-) diff --git a/SharpPad/wwwroot/fileSystem/fileManager.js b/SharpPad/wwwroot/fileSystem/fileManager.js index 3ca6e1a..ba1fe6e 100644 --- a/SharpPad/wwwroot/fileSystem/fileManager.js +++ b/SharpPad/wwwroot/fileSystem/fileManager.js @@ -44,10 +44,53 @@ class FileManager { addFolderBtn.addEventListener('click', () => this.addFolder()); } + // 文件列表折叠/展开按钮监听 + const toggleFileListBtn = document.getElementById('toggleFileList'); + if (toggleFileListBtn) { + toggleFileListBtn.addEventListener('click', () => this.toggleFileList()); + } + + // 恢复文件列表按钮监听 + const restoreFileListBtn = document.querySelector('.restore-filelist'); + if (restoreFileListBtn) { + restoreFileListBtn.addEventListener('click', () => this.toggleFileList()); + } + + // 初始化文件列表状态 + this.initializeFileListState(); + // 初始化右键菜单事件 this.initializeContextMenus(); } + initializeFileListState() { + const fileList = document.getElementById('fileList'); + const minimizedButton = document.querySelector('.minimized-filelist-button'); + const isCollapsed = localStorage.getItem('fileListCollapsed') === 'true'; + + if (isCollapsed && fileList && minimizedButton) { + fileList.classList.add('collapsed'); + minimizedButton.style.display = 'block'; + } + } + + toggleFileList() { + const fileList = document.getElementById('fileList'); + const minimizedButton = document.querySelector('.minimized-filelist-button'); + + if (!fileList || !minimizedButton) return; + + const isCollapsed = fileList.classList.toggle('collapsed'); + + if (isCollapsed) { + minimizedButton.style.display = 'block'; + localStorage.setItem('fileListCollapsed', 'true'); + } else { + minimizedButton.style.display = 'none'; + localStorage.setItem('fileListCollapsed', 'false'); + } + } + normalizeProjectType(projectType) { const fallback = 'console'; const candidate = typeof projectType === 'string' diff --git a/SharpPad/wwwroot/index.html b/SharpPad/wwwroot/index.html index 92be850..3a5cfa0 100644 --- a/SharpPad/wwwroot/index.html +++ b/SharpPad/wwwroot/index.html @@ -25,6 +25,7 @@
+
@@ -34,6 +35,11 @@
+
diff --git a/SharpPad/wwwroot/styles/fileList.css b/SharpPad/wwwroot/styles/fileList.css index 7b81a51..c57bf79 100644 --- a/SharpPad/wwwroot/styles/fileList.css +++ b/SharpPad/wwwroot/styles/fileList.css @@ -10,12 +10,18 @@ left: 0; top: 0; height: calc(100vh - 200px); - transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1), height 0.2s cubic-bezier(0.4, 0, 0.2, 1); - will-change: width, height; + transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1), + height 0.3s cubic-bezier(0.4, 0, 0.2, 1), + transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + will-change: width, height, transform; display: flex; flex-direction: column; } +#fileList.collapsed { + transform: translateX(-100%); +} + /* 亮色主题样式 */ body.theme-light #fileList { background-color: #f5f5f5; @@ -369,8 +375,87 @@ body.theme-light .sort-button:hover { color: #0066b8; } +/* 文件列表折叠/展开按钮 */ +#toggleFileList { + background: none; + border: none; + color: #cccccc; + cursor: pointer; + padding: 4px 8px; + font-size: 14px; + transition: all 0.2s; + border-radius: 3px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.7; +} + +#toggleFileList:hover { + background-color: rgba(255, 255, 255, 0.1); + opacity: 1; +} + +body.theme-light #toggleFileList { + color: #424242; +} + +body.theme-light #toggleFileList:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +/* 最小化文件列表按钮 */ +.minimized-filelist-button { + position: fixed; + left: 10px; + top: 50%; + transform: translateY(-50%); + z-index: 1000; +} + +.minimized-filelist-button .icon-button { + background: linear-gradient(135deg, #2979ff 0%, #1565c0 100%); + border: none; + color: #ffffff; + cursor: pointer; + padding: 10px 12px; + border-radius: 0 6px 6px 0; + font-size: 16px; + box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + align-items: center; + justify-content: center; + min-width: 36px; + min-height: 36px; +} + +.minimized-filelist-button .icon-button:hover { + background: linear-gradient(135deg, #2962ff 0%, #1976d2 100%); + transform: translateX(5px); + box-shadow: 3px 3px 12px rgba(0, 0, 0, 0.4); +} + +.minimized-filelist-button .icon-button:active { + transform: translateX(3px); +} + +body.theme-light .minimized-filelist-button .icon-button { + background: linear-gradient(135deg, #0066b8 0%, #005a9e 100%); + box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.2); +} + +body.theme-light .minimized-filelist-button .icon-button:hover { + background: linear-gradient(135deg, #005a9e 0%, #004b87 100%); + box-shadow: 3px 3px 12px rgba(0, 0, 0, 0.3); +} + @media only screen and (max-width: 768px) { #fileList { display: none; } + + .minimized-filelist-button { + display: none !important; + } } From 79198034ef9c28e3227b0e2376d3ed42b911264a Mon Sep 17 00:00:00 2001 From: gaocong Date: Tue, 28 Oct 2025 17:04:02 +0800 Subject: [PATCH 2/2] feat: Improve file list collapse/expand behavior and container width calculations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enhanced container width calculation to properly handle collapsed file list state - Added fileListResizer integration to sync width updates when toggling collapse state - Fixed issue where minimized button visibility wasn't properly managed - Improved responsive layout calculations for desktop mode with collapsed file list - Added proper handling of vertical output panel width in container calculations - Added COLLAPSED_FILE_LIST_OFFSET constant for consistent collapsed state offset 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- SharpPad/wwwroot/fileSystem/fileManager.js | 11 +++ SharpPad/wwwroot/styles/outputPanel.css | 2 +- SharpPad/wwwroot/utils/common.js | 110 +++++++++++++-------- 3 files changed, 82 insertions(+), 41 deletions(-) diff --git a/SharpPad/wwwroot/fileSystem/fileManager.js b/SharpPad/wwwroot/fileSystem/fileManager.js index ba1fe6e..ca47a95 100644 --- a/SharpPad/wwwroot/fileSystem/fileManager.js +++ b/SharpPad/wwwroot/fileSystem/fileManager.js @@ -2,6 +2,7 @@ import { showNotification, DEFAULT_CODE, PROJECT_TYPE_CHANGE_EVENT } from '../utils/common.js'; import { customPrompt, customConfirm } from '../utils/customPrompt.js'; import desktopBridge from '../utils/desktopBridge.js'; +import { fileListResizer } from './fileListResizer.js'; class FileManager { constructor() { @@ -71,6 +72,12 @@ class FileManager { if (isCollapsed && fileList && minimizedButton) { fileList.classList.add('collapsed'); minimizedButton.style.display = 'block'; + } else if (minimizedButton) { + minimizedButton.style.display = 'none'; + } + + if (fileListResizer && typeof fileListResizer.updateContainerWidth === 'function') { + fileListResizer.updateContainerWidth(); } } @@ -89,6 +96,10 @@ class FileManager { minimizedButton.style.display = 'none'; localStorage.setItem('fileListCollapsed', 'false'); } + + if (fileListResizer && typeof fileListResizer.updateContainerWidth === 'function') { + fileListResizer.updateContainerWidth(); + } } normalizeProjectType(projectType) { diff --git a/SharpPad/wwwroot/styles/outputPanel.css b/SharpPad/wwwroot/styles/outputPanel.css index c686664..c5e0cc4 100644 --- a/SharpPad/wwwroot/styles/outputPanel.css +++ b/SharpPad/wwwroot/styles/outputPanel.css @@ -723,4 +723,4 @@ body.theme-light .input-container input { body.theme-light #outputContent .markdown-content pre { background-color: #f5f5f5; border: 1px solid #e0e0e0; -} \ No newline at end of file +} diff --git a/SharpPad/wwwroot/utils/common.js b/SharpPad/wwwroot/utils/common.js index cc1e0d0..d8ec174 100644 --- a/SharpPad/wwwroot/utils/common.js +++ b/SharpPad/wwwroot/utils/common.js @@ -1,4 +1,6 @@ -import { extractDirectiveReferences, buildMultiFileContext } from './multiFileHelper.js'; +import { extractDirectiveReferences, buildMultiFileContext } from './multiFileHelper.js'; + +const COLLAPSED_FILE_LIST_OFFSET = 48; export const PROJECT_TYPE_CHANGE_EVENT = 'sharppad:projectTypeChanged'; @@ -200,45 +202,73 @@ export function getResponsiveSize(defaultSize, mobileSize) { } // 响应式设置container宽度 -export function setContainerWidth(container, fileListWidth, chatPanelWidth, chatPanelVisible) { - // 使用媒体查询检测移动设备 - const isMobile = window.matchMedia('(max-width: 768px) and (pointer: coarse), (max-width: 480px)').matches; - - if (isMobile) { - // 移动设备适配 - if (chatPanelVisible) { - // 移动设备上聊天面板设置为全屏宽度,主容器隐藏或调整到最小 - container.style.width = '100%'; - container.style.marginRight = '0'; - - // 根据聊天面板的位置决定编辑器的样式 - // 如果聊天面板在屏幕下半部分 - container.style.height = 'calc(50vh - 1px)'; - container.style.marginBottom = '0'; - } else { - // 聊天面板不可见时,编辑器占满全屏 - container.style.width = '100%'; - container.style.height = '100vh'; - container.style.marginRight = '0'; - container.style.marginBottom = '0'; - } - - // 在移动设备上,文件列表可能会是覆盖式的,而不是并排 - container.style.marginLeft = '0'; - } else { - // 桌面设备使用原来的布局逻辑 - if (chatPanelVisible) { - container.style.width = `calc(100% - ${fileListWidth}px - ${chatPanelWidth}px)`; - container.style.marginRight = `${chatPanelWidth}px`; - } else { - container.style.width = `calc(100% - ${fileListWidth}px)`; - container.style.marginRight = '0'; - } - container.style.marginLeft = `${fileListWidth}px`; - container.style.height = '100vh'; - } - - // 重新布局编辑器 +export function setContainerWidth(container, fileListWidth, chatPanelWidth, chatPanelVisible) { + if (!container) { + return; + } + + const fileListElement = document.getElementById('fileList'); + const isFileListCollapsed = fileListElement?.classList.contains('collapsed'); + const normalizedFileListWidth = Number.isFinite(fileListWidth) ? fileListWidth : 0; + const effectiveFileListWidth = isFileListCollapsed ? 0 : normalizedFileListWidth; + + // 使用媒体查询检测移动设备 + const isMobile = window.matchMedia('(max-width: 768px) and (pointer: coarse), (max-width: 480px)').matches; + const collapsedOffset = isFileListCollapsed && !isMobile ? COLLAPSED_FILE_LIST_OFFSET : 0; + + if (isMobile) { + // 移动设备适配 + if (chatPanelVisible) { + // 移动设备上聊天面板设置为全屏宽度,主容器隐藏或调整到最小 + container.style.width = '100%'; + container.style.marginRight = '0'; + + // 根据聊天面板的位置决定编辑器的样式 + // 如果聊天面板在屏幕下半部分 + container.style.height = 'calc(50vh - 1px)'; + container.style.marginBottom = '0'; + } else { + // 聊天面板不可见时,编辑器占满全屏 + container.style.width = '100%'; + container.style.height = '100vh'; + container.style.marginRight = '0'; + container.style.marginBottom = '0'; + } + + // 在移动设备上,文件列表可能会是覆盖式的,而不是并排 + container.style.marginLeft = '0'; + container.style.marginRight = '0'; + } else { + // 桌面设备使用原来的布局逻辑 + const totalFileListOffset = effectiveFileListWidth + collapsedOffset; + const normalizedChatWidth = chatPanelVisible && Number.isFinite(chatPanelWidth) + ? chatPanelWidth + : 0; + + let outputPanelWidth = 0; + const outputPanel = document.getElementById('outputPanel'); + if (outputPanel) { + const outputStyles = window.getComputedStyle(outputPanel); + const isVerticalOutput = outputPanel.classList.contains('vertical') && outputStyles.display !== 'none'; + if (isVerticalOutput) { + const parsedWidth = parseInt(outputStyles.width, 10); + if (Number.isFinite(parsedWidth)) { + outputPanelWidth = parsedWidth; + } + } + } + + // 考虑垂直输出面板的宽度,避免折叠文件列表时挤压输出区域 + const totalRightOffset = normalizedChatWidth + outputPanelWidth; + const rightOffsetTerm = totalRightOffset > 0 ? ` - ${totalRightOffset}px` : ''; + + container.style.width = `calc(100% - ${totalFileListOffset}px${rightOffsetTerm})`; + container.style.marginRight = `${totalRightOffset}px`; + container.style.marginLeft = `${totalFileListOffset}px`; + container.style.height = '100vh'; + } + + // 重新布局编辑器 layoutEditor(); }