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;
|
||
}
|
||
}
|