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
54 changes: 54 additions & 0 deletions SharpPad/wwwroot/fileSystem/fileManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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'
Expand Down
6 changes: 6 additions & 0 deletions SharpPad/wwwroot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<body class="theme-dark">
<div id="fileList" style="max-width: 800px; width: 380px; min-width: 340px;">
<div class="file-list-header">
<button id="toggleFileList" title="收起/展开">◀</button>
<input type="text" id="fileFilter" placeholder="搜索文件...">
<div style="display: flex; gap: 5px;">
<button id="addFolderBtn" title="新建文件夹">📁</button>
Expand All @@ -34,6 +35,11 @@
<ul id="fileListItems">
</ul>
</div>
<div class="minimized-filelist-button" style="display: none;">
<button class="icon-button restore-filelist" title="文件列表">
<span style="transform: rotate(180deg);">▶</span>
</button>
</div>
<div id="container">
<button class="editor-control-button theme-button" id="themeButton" title="切换主题"></button>
<div class="button-container">
Expand Down
89 changes: 87 additions & 2 deletions SharpPad/wwwroot/styles/fileList.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
110 changes: 70 additions & 40 deletions SharpPad/wwwroot/utils/common.js
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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();
}

Expand Down