468 lines
17 KiB
JavaScript
468 lines
17 KiB
JavaScript
// 文件管理器 - 处理流程文件的加载、保存和管理
|
||
|
||
function FileManager() {
|
||
this.currentFileName = "新流程.json";
|
||
this.currentDirectory = "默认目录"; // 当前工作目录
|
||
this.processFiles = new Map(); // 存储所有流程文件数据,key为完整路径
|
||
this.directoryHandle = null; // File System Access API 目录句柄
|
||
this.supportsFileSystemAccess = false; // 是否支持 File System Access API
|
||
this.initializeDefaultFiles();
|
||
this.checkFileSystemAccessSupport();
|
||
}
|
||
|
||
// 初始化默认文件
|
||
FileManager.prototype.initializeDefaultFiles = function() {
|
||
// 创建一个默认的空流程文件
|
||
this.processFiles.set("新流程.json", []);
|
||
|
||
// 可以添加一些示例文件
|
||
this.addSampleFile();
|
||
};
|
||
|
||
// 添加示例文件
|
||
FileManager.prototype.addSampleFile = function() {
|
||
var sampleStep = DataUtils.createNewStep();
|
||
sampleStep.StepDescription = "示例步骤";
|
||
|
||
var sampleAction = DataUtils.createNewAction();
|
||
sampleAction.Title = "示例动作";
|
||
sampleAction.Description = "这是一个示例动作";
|
||
|
||
var sampleTarget = DataUtils.createNewTargetObject();
|
||
sampleTarget.ObjectName = "示例对象";
|
||
sampleTarget.Type = ProcessTargetType.EVENT;
|
||
sampleTarget.Score = 10;
|
||
|
||
sampleAction.TargetObjects.push(sampleTarget);
|
||
sampleStep.Actions.push(sampleAction);
|
||
|
||
this.processFiles.set("示例流程.json", [sampleStep]);
|
||
};
|
||
|
||
// 获取所有文件名
|
||
FileManager.prototype.getFileNames = function() {
|
||
return Array.from(this.processFiles.keys());
|
||
};
|
||
|
||
// 获取当前文件名
|
||
FileManager.prototype.getCurrentFileName = function() {
|
||
return this.currentFileName;
|
||
};
|
||
|
||
// 设置当前文件
|
||
FileManager.prototype.setCurrentFile = function(fileName) {
|
||
if (this.processFiles.has(fileName)) {
|
||
this.currentFileName = fileName;
|
||
return true;
|
||
}
|
||
return false;
|
||
};
|
||
|
||
// 加载流程文件
|
||
FileManager.prototype.loadProcessFile = function(fileName) {
|
||
if (this.processFiles.has(fileName)) {
|
||
var data = this.processFiles.get(fileName);
|
||
return DataUtils.deepClone(data);
|
||
}
|
||
return [];
|
||
};
|
||
|
||
// 保存流程文件
|
||
FileManager.prototype.saveProcessFile = function(fileName, steps) {
|
||
try {
|
||
// 验证数据
|
||
for (var i = 0; i < steps.length; i++) {
|
||
var validation = DataUtils.validateStep(steps[i]);
|
||
if (!validation.valid) {
|
||
throw new Error("步骤 " + (i + 1) + ": " + validation.message);
|
||
}
|
||
}
|
||
|
||
// 清理数据
|
||
var cleanedSteps = [];
|
||
for (var i = 0; i < steps.length; i++) {
|
||
var cleanedStep = DataUtils.deepClone(steps[i]);
|
||
var cleanedActions = [];
|
||
for (var j = 0; j < cleanedStep.Actions.length; j++) {
|
||
cleanedActions.push(DataUtils.cleanActionData(cleanedStep.Actions[j]));
|
||
}
|
||
cleanedStep.Actions = cleanedActions;
|
||
cleanedSteps.push(cleanedStep);
|
||
}
|
||
|
||
// 保存到内存
|
||
this.processFiles.set(fileName, cleanedSteps);
|
||
this.currentFileName = fileName;
|
||
|
||
// 如果支持本地存储,也保存到localStorage
|
||
if (typeof Storage !== "undefined") {
|
||
var allFiles = {};
|
||
var self = this;
|
||
this.processFiles.forEach(function(value, key) {
|
||
allFiles[key] = value;
|
||
});
|
||
localStorage.setItem('sceneStepEditor_files', JSON.stringify(allFiles));
|
||
}
|
||
|
||
return { success: true };
|
||
} catch (error) {
|
||
return { success: false, error: error.message };
|
||
}
|
||
};
|
||
|
||
// 创建新流程文件
|
||
FileManager.prototype.createNewProcessFile = function(fileName) {
|
||
if (!fileName || fileName.trim() === "") {
|
||
return { success: false, error: "文件名不能为空" };
|
||
}
|
||
|
||
// 确保文件名以.json结尾
|
||
if (!fileName.endsWith('.json')) {
|
||
fileName += '.json';
|
||
}
|
||
|
||
// 检查文件是否已存在
|
||
if (this.processFiles.has(fileName)) {
|
||
return { success: false, error: "文件已存在" };
|
||
}
|
||
|
||
// 创建新文件
|
||
this.processFiles.set(fileName, []);
|
||
this.currentFileName = fileName;
|
||
|
||
return { success: true, fileName: fileName };
|
||
};
|
||
|
||
// 获取当前目录
|
||
FileManager.prototype.getCurrentDirectory = function() {
|
||
return this.currentDirectory;
|
||
};
|
||
|
||
// 设置当前目录
|
||
FileManager.prototype.setCurrentDirectory = function(directoryName) {
|
||
this.currentDirectory = directoryName || "默认目录";
|
||
};
|
||
|
||
// 从目录中加载文件
|
||
FileManager.prototype.loadFilesFromDirectory = function(files) {
|
||
var self = this;
|
||
var loadedCount = 0;
|
||
var totalFiles = 0;
|
||
|
||
// 计算json文件总数
|
||
for (var i = 0; i < files.length; i++) {
|
||
if (files[i].name.toLowerCase().endsWith('.json')) {
|
||
totalFiles++;
|
||
}
|
||
}
|
||
|
||
if (totalFiles === 0) {
|
||
return Promise.resolve({ success: false, error: "所选目录中没有找到JSON文件" });
|
||
}
|
||
|
||
return new Promise(function(resolve) {
|
||
// 清空当前文件(保留默认文件)
|
||
var keysToDelete = [];
|
||
self.processFiles.forEach(function(value, key) {
|
||
if (key !== "新流程.json" && key !== "示例流程.json") {
|
||
keysToDelete.push(key);
|
||
}
|
||
});
|
||
keysToDelete.forEach(function(key) {
|
||
self.processFiles.delete(key);
|
||
});
|
||
|
||
// 设置目录名(使用第一个文件的父目录名)
|
||
if (files.length > 0 && files[0].webkitRelativePath) {
|
||
var pathParts = files[0].webkitRelativePath.split('/');
|
||
if (pathParts.length > 1) {
|
||
self.currentDirectory = pathParts[0];
|
||
}
|
||
}
|
||
|
||
// 逐个读取JSON文件
|
||
for (var i = 0; i < files.length; i++) {
|
||
var file = files[i];
|
||
if (file.name.toLowerCase().endsWith('.json')) {
|
||
// 使用闭包捕获当前文件对象,避免异步回调时变量引用错误
|
||
(function(currentFile) {
|
||
self.readFileContent(currentFile, function(fileName, content, error) {
|
||
loadedCount++;
|
||
|
||
if (!error) {
|
||
try {
|
||
var data = JSON.parse(content);
|
||
// 验证数据格式
|
||
if (Array.isArray(data)) {
|
||
// 使用相对路径作为key
|
||
var relativePath = fileName;
|
||
if (currentFile.webkitRelativePath) {
|
||
// 移除目录前缀,只保留文件名
|
||
var parts = currentFile.webkitRelativePath.split('/');
|
||
relativePath = parts[parts.length - 1];
|
||
}
|
||
self.processFiles.set(relativePath, data);
|
||
console.log("成功加载文件: " + relativePath);
|
||
}
|
||
} catch (parseError) {
|
||
console.warn("无法解析JSON文件: " + fileName, parseError);
|
||
}
|
||
} else {
|
||
console.warn("读取文件失败: " + fileName, error);
|
||
}
|
||
|
||
// 所有文件处理完成
|
||
if (loadedCount === totalFiles) {
|
||
console.log("目录加载完成,总计加载文件: " + (self.processFiles.size - 2));
|
||
resolve({
|
||
success: true,
|
||
loadedFiles: self.processFiles.size - 2, // 减去默认文件数量
|
||
directory: self.currentDirectory
|
||
});
|
||
}
|
||
});
|
||
})(file);
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
// 读取文件内容
|
||
FileManager.prototype.readFileContent = function(file, callback) {
|
||
var reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
callback(file.name, e.target.result, null);
|
||
};
|
||
reader.onerror = function(e) {
|
||
callback(file.name, null, e);
|
||
};
|
||
reader.readAsText(file, 'utf-8');
|
||
};
|
||
|
||
// 获取当前目录下的文件(用于下拉列表显示)
|
||
FileManager.prototype.getFilesInCurrentDirectory = function() {
|
||
var files = [];
|
||
var self = this;
|
||
this.processFiles.forEach(function(value, key) {
|
||
// 如果是默认文件或者不包含路径分隔符,直接添加
|
||
if (key === "新流程.json" || key === "示例流程.json" || key.indexOf('/') === -1) {
|
||
files.push(key);
|
||
}
|
||
});
|
||
return files;
|
||
};
|
||
|
||
// 创建新流程文件(保存到当前目录)
|
||
FileManager.prototype.createNewProcessFileInCurrentDir = function(fileName) {
|
||
if (!fileName || fileName.trim() === "") {
|
||
return { success: false, error: "文件名不能为空" };
|
||
}
|
||
|
||
// 确保文件名以.json结尾
|
||
if (!fileName.endsWith('.json')) {
|
||
fileName += '.json';
|
||
}
|
||
|
||
// 构建完整路径(当前目录下的文件)
|
||
var fullPath = fileName; // 简化路径,直接使用文件名
|
||
|
||
// 检查文件是否已存在
|
||
if (this.processFiles.has(fullPath)) {
|
||
return { success: false, error: "文件已存在" };
|
||
}
|
||
|
||
// 创建新文件
|
||
this.processFiles.set(fullPath, []);
|
||
this.currentFileName = fullPath;
|
||
|
||
return { success: true, fileName: fullPath, directory: this.currentDirectory };
|
||
};
|
||
|
||
// 检查 File System Access API 支持
|
||
FileManager.prototype.checkFileSystemAccessSupport = function() {
|
||
this.supportsFileSystemAccess = 'showDirectoryPicker' in window && 'showSaveFilePicker' in window;
|
||
console.log("File System Access API 支持状态:", this.supportsFileSystemAccess);
|
||
};
|
||
|
||
// 使用 File System Access API 选择目录
|
||
FileManager.prototype.selectDirectoryWithAPI = function() {
|
||
var self = this;
|
||
if (!this.supportsFileSystemAccess) {
|
||
return Promise.reject(new Error("浏览器不支持 File System Access API"));
|
||
}
|
||
|
||
return window.showDirectoryPicker().then(function(handle) {
|
||
self.directoryHandle = handle;
|
||
self.currentDirectory = handle.name;
|
||
console.log("已选择目录:", handle.name);
|
||
|
||
// 存储目录句柄到 IndexedDB 以便下次使用
|
||
self.storeDirectoryHandle(handle);
|
||
|
||
return self.loadFilesFromDirectoryHandle(handle);
|
||
});
|
||
};
|
||
|
||
// 存储目录句柄到 IndexedDB
|
||
FileManager.prototype.storeDirectoryHandle = function(handle) {
|
||
if (typeof indexedDB !== 'undefined') {
|
||
var request = indexedDB.open('SceneStepEditor', 1);
|
||
request.onupgradeneeded = function(e) {
|
||
var db = e.target.result;
|
||
if (!db.objectStoreNames.contains('directoryHandles')) {
|
||
db.createObjectStore('directoryHandles');
|
||
}
|
||
};
|
||
request.onsuccess = function(e) {
|
||
var db = e.target.result;
|
||
var transaction = db.transaction(['directoryHandles'], 'readwrite');
|
||
var store = transaction.objectStore('directoryHandles');
|
||
store.put(handle, 'currentDirectory');
|
||
console.log("目录句柄已保存到 IndexedDB");
|
||
};
|
||
}
|
||
};
|
||
|
||
// 从 IndexedDB 恢复目录句柄
|
||
FileManager.prototype.restoreDirectoryHandle = function() {
|
||
var self = this;
|
||
if (typeof indexedDB === 'undefined' || !this.supportsFileSystemAccess) {
|
||
return Promise.resolve(null);
|
||
}
|
||
|
||
return new Promise(function(resolve) {
|
||
var request = indexedDB.open('SceneStepEditor', 1);
|
||
request.onsuccess = function(e) {
|
||
var db = e.target.result;
|
||
if (!db.objectStoreNames.contains('directoryHandles')) {
|
||
resolve(null);
|
||
return;
|
||
}
|
||
|
||
var transaction = db.transaction(['directoryHandles'], 'readonly');
|
||
var store = transaction.objectStore('directoryHandles');
|
||
var getRequest = store.get('currentDirectory');
|
||
|
||
getRequest.onsuccess = function() {
|
||
var handle = getRequest.result;
|
||
if (handle) {
|
||
// 验证句柄是否仍然有效
|
||
handle.queryPermission().then(function(permission) {
|
||
if (permission === 'granted') {
|
||
self.directoryHandle = handle;
|
||
self.currentDirectory = handle.name;
|
||
console.log("已恢复目录句柄:", handle.name);
|
||
resolve(handle);
|
||
} else {
|
||
resolve(null);
|
||
}
|
||
}).catch(function() {
|
||
resolve(null);
|
||
});
|
||
} else {
|
||
resolve(null);
|
||
}
|
||
};
|
||
|
||
getRequest.onerror = function() {
|
||
resolve(null);
|
||
};
|
||
};
|
||
|
||
request.onerror = function() {
|
||
resolve(null);
|
||
};
|
||
});
|
||
};
|
||
|
||
// 从目录句柄加载文件
|
||
FileManager.prototype.loadFilesFromDirectoryHandle = function(directoryHandle) {
|
||
var self = this;
|
||
|
||
return new Promise(async function(resolve) {
|
||
try {
|
||
var loadedFiles = 0;
|
||
var filePromises = [];
|
||
|
||
// 清空当前文件(保留默认文件)
|
||
var keysToDelete = [];
|
||
self.processFiles.forEach(function(value, key) {
|
||
if (key !== "新流程.json" && key !== "示例流程.json") {
|
||
keysToDelete.push(key);
|
||
}
|
||
});
|
||
keysToDelete.forEach(function(key) {
|
||
self.processFiles.delete(key);
|
||
});
|
||
|
||
// 使用 for await...of 遍历目录中的文件
|
||
for await (const entry of directoryHandle.values()) {
|
||
if (entry.kind === 'file' && entry.name.toLowerCase().endsWith('.json')) {
|
||
var promise = entry.getFile().then(function(file) {
|
||
return file.text().then(function(content) {
|
||
try {
|
||
var data = JSON.parse(content);
|
||
if (Array.isArray(data)) {
|
||
self.processFiles.set(entry.name, data);
|
||
loadedFiles++;
|
||
console.log("成功加载文件:", entry.name);
|
||
}
|
||
} catch (parseError) {
|
||
console.warn("无法解析JSON文件:", entry.name, parseError);
|
||
}
|
||
});
|
||
}).catch(function(error) {
|
||
console.warn("读取文件失败:", entry.name, error);
|
||
});
|
||
|
||
filePromises.push(promise);
|
||
}
|
||
}
|
||
|
||
// 等待所有文件读取完成
|
||
await Promise.all(filePromises);
|
||
|
||
resolve({
|
||
success: true,
|
||
loadedFiles: loadedFiles,
|
||
directory: directoryHandle.name
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error("遍历目录失败:", error);
|
||
resolve({ success: false, error: error.message });
|
||
}
|
||
});
|
||
};
|
||
|
||
// 直接写入文件到选择的目录
|
||
FileManager.prototype.writeFileToDirectory = function(fileName, data) {
|
||
var self = this;
|
||
|
||
if (!this.supportsFileSystemAccess || !this.directoryHandle) {
|
||
return Promise.reject(new Error("未选择目录或浏览器不支持直接写入"));
|
||
}
|
||
|
||
// 确保文件名以.json结尾
|
||
if (!fileName.endsWith('.json')) {
|
||
fileName += '.json';
|
||
}
|
||
|
||
return this.directoryHandle.getFileHandle(fileName, { create: true }).then(function(fileHandle) {
|
||
return fileHandle.createWritable().then(function(writable) {
|
||
var jsonString = JSON.stringify(data, null, 2);
|
||
return writable.write(jsonString).then(function() {
|
||
return writable.close().then(function() {
|
||
console.log("文件已写入目录:", fileName);
|
||
|
||
// 更新内存中的数据
|
||
self.processFiles.set(fileName, data);
|
||
self.currentFileName = fileName;
|
||
|
||
return { success: true, fileName: fileName, directory: self.currentDirectory };
|
||
});
|
||
});
|
||
});
|
||
});
|
||
};
|