281 lines
9.0 KiB
JavaScript
281 lines
9.0 KiB
JavaScript
|
/**
|
|||
|
* 文件操作模块
|
|||
|
* @type {module}
|
|||
|
*/
|
|||
|
import { formatXml, markEdited, resetEditState } from './utils.js';
|
|||
|
|
|||
|
// 获取Scenario目录路径前缀
|
|||
|
let scenarioDirPrefix = null;
|
|||
|
|
|||
|
/**
|
|||
|
* 获取Scenario目录路径
|
|||
|
* @returns {string} Scenario目录的绝对路径
|
|||
|
*/
|
|||
|
async function getScenarioDir() {
|
|||
|
if (scenarioDirPrefix) return scenarioDirPrefix;
|
|||
|
|
|||
|
try {
|
|||
|
// 从文件列表API获取Scenario目录路径
|
|||
|
const response = await fetch('/api/scenario-files');
|
|||
|
if (response.ok) {
|
|||
|
const files = await response.json();
|
|||
|
if (files && files.length > 0) {
|
|||
|
// 提取第一个文件的目录作为Scenario目录
|
|||
|
const filePath = files[0].path;
|
|||
|
scenarioDirPrefix = filePath.substring(0, filePath.lastIndexOf('/'));
|
|||
|
return scenarioDirPrefix;
|
|||
|
}
|
|||
|
}
|
|||
|
return ''; // 如果无法获取,返回空字符串
|
|||
|
} catch (error) {
|
|||
|
console.error('获取Scenario目录失败:', error);
|
|||
|
return '';
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 加载场景文件列表
|
|||
|
* @param {HTMLElement} component - 组件实例
|
|||
|
* @returns {Promise<boolean>} 是否成功加载
|
|||
|
*/
|
|||
|
export async function loadScenarioFiles(component) {
|
|||
|
// 设置按钮为刷新中状态
|
|||
|
const refreshButton = component.shadowRoot.getElementById('refreshButton');
|
|||
|
if (refreshButton) {
|
|||
|
refreshButton.classList.add('refreshing');
|
|||
|
}
|
|||
|
|
|||
|
let retryCount = 0;
|
|||
|
const maxRetries = 3; // 最多重试3次
|
|||
|
|
|||
|
const tryLoadFiles = async () => {
|
|||
|
try {
|
|||
|
const response = await fetch('/api/scenario-files');
|
|||
|
|
|||
|
if (!response.ok) {
|
|||
|
throw new Error(`服务器返回错误: ${response.status} ${response.statusText}`);
|
|||
|
}
|
|||
|
|
|||
|
const files = await response.json();
|
|||
|
component.scenarioFiles = files;
|
|||
|
|
|||
|
// 更新文件选择器
|
|||
|
updateFileSelector(component);
|
|||
|
|
|||
|
return true;
|
|||
|
} catch (error) {
|
|||
|
if (retryCount < maxRetries) {
|
|||
|
retryCount++;
|
|||
|
// 指数级退避重试
|
|||
|
const retryDelay = Math.pow(2, retryCount) * 500;
|
|||
|
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|||
|
return tryLoadFiles();
|
|||
|
}
|
|||
|
|
|||
|
alert(`加载文件列表失败: ${error.message}`);
|
|||
|
return false;
|
|||
|
} finally {
|
|||
|
// 无论成功失败,移除刷新中状态
|
|||
|
if (refreshButton) {
|
|||
|
refreshButton.classList.remove('refreshing');
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
const success = await tryLoadFiles();
|
|||
|
|
|||
|
// 检查是否是用户通过刷新按钮手动触发的刷新
|
|||
|
const isManualRefresh = document.activeElement === refreshButton;
|
|||
|
|
|||
|
if (success && isManualRefresh) {
|
|||
|
// 只有在用户明确点击刷新按钮时才清空当前文件
|
|||
|
component.currentFile = null;
|
|||
|
component.xmlContent = '';
|
|||
|
component.updateFileContent();
|
|||
|
component.isEdited = resetEditState(component.shadowRoot);
|
|||
|
|
|||
|
// 更新文件选择器,确保没有文件被选中
|
|||
|
const fileSelector = component.shadowRoot.getElementById('scenarioFile');
|
|||
|
if (fileSelector) {
|
|||
|
fileSelector.value = '';
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return success;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 更新文件选择器
|
|||
|
* @param {HTMLElement} component - 组件实例
|
|||
|
*/
|
|||
|
export function updateFileSelector(component) {
|
|||
|
const fileSelector = component.shadowRoot.getElementById('scenarioFile');
|
|||
|
if (fileSelector) {
|
|||
|
// 清除现有选项,保留第一个
|
|||
|
while (fileSelector.options.length > 1) {
|
|||
|
fileSelector.remove(1);
|
|||
|
}
|
|||
|
|
|||
|
// 添加文件选项
|
|||
|
component.scenarioFiles.forEach(file => {
|
|||
|
const option = document.createElement('option');
|
|||
|
option.value = file.path;
|
|||
|
option.textContent = file.name;
|
|||
|
fileSelector.appendChild(option);
|
|||
|
});
|
|||
|
|
|||
|
// 验证文件选择器选项
|
|||
|
const options = Array.from(fileSelector.options);
|
|||
|
options.forEach((opt, index) => {
|
|||
|
if (index === 0) return; // 跳过第一个空选项
|
|||
|
});
|
|||
|
} else {
|
|||
|
console.log('未找到文件选择器元素');
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 加载文件内容
|
|||
|
* @param {HTMLElement} component - 组件实例
|
|||
|
* @param {string} filePath - 文件路径
|
|||
|
* @returns {Promise<void>}
|
|||
|
*/
|
|||
|
export async function loadFileContent(component, filePath) {
|
|||
|
try {
|
|||
|
const response = await fetch(`/api/file-content?path=${encodeURIComponent(filePath)}`);
|
|||
|
if (!response.ok) {
|
|||
|
throw new Error(`加载文件内容失败: ${response.status} ${response.statusText}`);
|
|||
|
}
|
|||
|
|
|||
|
component.xmlContent = await response.text();
|
|||
|
component.currentFile = filePath;
|
|||
|
|
|||
|
// 检查内容是否为空
|
|||
|
if (!component.xmlContent.trim()) {
|
|||
|
// 如果是.sce文件且内容为空,则报文件内容为空的错误
|
|||
|
if (filePath.toLowerCase().endsWith('.sce')) {
|
|||
|
throw new Error('文件内容为空');
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
component.updateFileContent();
|
|||
|
|
|||
|
// 重置编辑状态,因为刚刚加载了新文件
|
|||
|
component.isEdited = resetEditState(component.shadowRoot);
|
|||
|
} catch (error) {
|
|||
|
console.error('加载文件内容失败:', error);
|
|||
|
component.xmlContent = '<!-- 加载文件内容失败 -->';
|
|||
|
component.updateFileContent();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 保存文件内容
|
|||
|
* @param {HTMLElement} component - 组件实例
|
|||
|
* @param {string} filePath - 文件路径
|
|||
|
* @param {string} content - 文件内容
|
|||
|
* @returns {Promise<boolean>} 是否成功保存
|
|||
|
*/
|
|||
|
export async function saveFileContent(component, filePath, content) {
|
|||
|
try {
|
|||
|
// 更新XML内容
|
|||
|
if (component.autoSaveToXml) {
|
|||
|
component.updateXmlFromVisualEditor();
|
|||
|
}
|
|||
|
|
|||
|
// 格式化XML内容
|
|||
|
const formattedContent = formatXml(component.xmlContent);
|
|||
|
|
|||
|
// 确保使用绝对路径
|
|||
|
let absolutePath = filePath;
|
|||
|
if (!filePath.startsWith('/')) {
|
|||
|
const scenarioDir = await getScenarioDir();
|
|||
|
// 如果filePath是相对路径但带有./,则移除它
|
|||
|
const cleanPath = filePath.startsWith('./') ? filePath.substring(2) : filePath;
|
|||
|
absolutePath = `${scenarioDir}/${cleanPath}`;
|
|||
|
}
|
|||
|
|
|||
|
const response = await fetch('/api/save-file', {
|
|||
|
method: 'POST',
|
|||
|
headers: {
|
|||
|
'Content-Type': 'application/json'
|
|||
|
},
|
|||
|
body: JSON.stringify({
|
|||
|
path: absolutePath,
|
|||
|
content: formattedContent
|
|||
|
})
|
|||
|
});
|
|||
|
|
|||
|
if (!response.ok) {
|
|||
|
throw new Error(`保存文件失败: ${response.status} ${response.statusText}`);
|
|||
|
}
|
|||
|
|
|||
|
// 重置编辑状态
|
|||
|
component.isEdited = resetEditState(component.shadowRoot);
|
|||
|
|
|||
|
// 更新当前文件
|
|||
|
component.currentFile = absolutePath;
|
|||
|
|
|||
|
// 刷新文件列表
|
|||
|
await loadScenarioFiles(component);
|
|||
|
|
|||
|
// 选中当前文件
|
|||
|
const fileSelector = component.shadowRoot.getElementById('scenarioFile');
|
|||
|
if (fileSelector && component.currentFile) {
|
|||
|
fileSelector.value = component.currentFile;
|
|||
|
}
|
|||
|
|
|||
|
alert('文件保存成功');
|
|||
|
return true;
|
|||
|
} catch (error) {
|
|||
|
console.error('保存文件失败:', error);
|
|||
|
alert(`保存文件失败: ${error.message}`);
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 创建新配置
|
|||
|
* @param {HTMLElement} component - 组件实例
|
|||
|
* @param {string} fileName - 文件名
|
|||
|
* @returns {Promise<boolean>} 是否成功创建
|
|||
|
*/
|
|||
|
export async function createNewConfig(component, fileName) {
|
|||
|
try {
|
|||
|
// 使用服务器API创建新文件
|
|||
|
const response = await fetch('/api/create-config-file', {
|
|||
|
method: 'POST',
|
|||
|
headers: {
|
|||
|
'Content-Type': 'application/json'
|
|||
|
},
|
|||
|
body: JSON.stringify({
|
|||
|
fileName: fileName
|
|||
|
})
|
|||
|
});
|
|||
|
|
|||
|
if (!response.ok) {
|
|||
|
const errorData = await response.json();
|
|||
|
throw new Error(`创建文件失败: ${errorData.error || response.statusText}`);
|
|||
|
}
|
|||
|
|
|||
|
const data = await response.json();
|
|||
|
|
|||
|
// 更新文件列表
|
|||
|
await loadScenarioFiles(component);
|
|||
|
|
|||
|
// 加载新创建的文件内容
|
|||
|
await loadFileContent(component, data.path);
|
|||
|
|
|||
|
// 选中新创建的文件
|
|||
|
const fileSelector = component.shadowRoot.getElementById('scenarioFile');
|
|||
|
if (fileSelector) {
|
|||
|
fileSelector.value = data.path;
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
} catch (error) {
|
|||
|
console.error('创建新配置文件失败:', error);
|
|||
|
alert(`创建新配置文件失败: ${error.message}`);
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|