diff --git a/SharpPad/wwwroot/fileSystem/fileManager.js b/SharpPad/wwwroot/fileSystem/fileManager.js index d83cbbf..38bacfd 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() { @@ -44,10 +45,63 @@ 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'; + } else if (minimizedButton) { + minimizedButton.style.display = 'none'; + } + + if (fileListResizer && typeof fileListResizer.updateContainerWidth === 'function') { + fileListResizer.updateContainerWidth(); + } + } + + 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'); + } + + if (fileListResizer && typeof fileListResizer.updateContainerWidth === 'function') { + fileListResizer.updateContainerWidth(); + } + } + normalizeProjectType(projectType) { const fallback = 'console'; const candidate = typeof projectType === 'string' diff --git a/SharpPad/wwwroot/index.html b/SharpPad/wwwroot/index.html index af85b0b..c5632f4 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; + } } 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(); }