XNSim/XNSimHtml/components/interface-config.js
2025-04-28 12:25:20 +08:00

2120 lines
81 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class InterfaceConfig extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.idlContent = '';
this.filePath = '';
this.isEdited = false;
}
connectedCallback() {
this.render();
this.addEventListeners();
// 加载IDL文件列表到下拉框
this.loadIdlFileList();
}
addEventListeners() {
// 新建文件按钮
this.shadowRoot.getElementById('new-file').addEventListener('click', () => this.handleNewFile());
// 文件选择下拉框
this.shadowRoot.getElementById('file-select-dropdown').addEventListener('change', (e) => {
if (e.target.value) {
this.handleFileChange(e.target.value);
}
});
// 保存按钮
this.shadowRoot.getElementById('save-button').addEventListener('click', () => this.saveFile());
// 另存为按钮
this.shadowRoot.getElementById('save-as-button').addEventListener('click', () => this.saveFileAs());
// 文本编辑器内容变化时更新可视化编辑器和编辑状态
this.shadowRoot.getElementById('idl-editor').addEventListener('input', () => {
this.idlContent = this.shadowRoot.getElementById('idl-editor').value;
this.updateVisualEditor();
this.setEditedState(true);
});
}
async selectFile() {
try {
// 获取IDL文件列表
const response = await fetch('/api/idl/list');
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || '获取文件列表失败');
}
const data = await response.json();
// 显示文件选择对话框
if (!data.files || data.files.length === 0) {
this.showConfirmDialog('文件列表', '没有可用的IDL文件', null);
return;
}
// 创建文件选择对话框
const fileSelector = document.createElement('div');
fileSelector.className = 'method-dialog';
const fileListHtml = data.files.map(file => `
<div class="file-item" data-filename="${file.name}">
<span class="file-name">${file.name}</span>
<span class="file-info">
大小: ${this.formatFileSize(file.size)},
修改时间: ${new Date(file.modified).toLocaleString()}
</span>
</div>
`).join('');
fileSelector.innerHTML = `
<div class="dialog-content file-selector-dialog">
<h3>选择IDL文件</h3>
<div class="file-list">
${fileListHtml}
</div>
<div class="dialog-actions">
<button id="dialog-cancel">取消</button>
</div>
</div>
`;
this.shadowRoot.appendChild(fileSelector);
// 添加文件项点击事件
fileSelector.querySelectorAll('.file-item').forEach(item => {
item.addEventListener('click', async () => {
const filename = item.dataset.filename;
fileSelector.remove();
// 加载选定的文件
await this.loadFile(filename);
});
});
// 对话框取消事件
this.shadowRoot.getElementById('dialog-cancel').addEventListener('click', () => {
fileSelector.remove();
});
} catch (error) {
// console.error('选择文件时出错:', error);
// this.showConfirmDialog('选择文件错误', '选择文件时出错: ' + error.message, null);
}
}
parseIdlContent() {
// 针对XNAerodynamics.idl文件格式优化
const editor = this.shadowRoot.getElementById('idl-editor');
editor.value = this.idlContent;
// 更新可视化编辑区域
this.updateVisualEditor();
}
updateVisualEditor() {
// 从文本解析IDL结构
const visualEditor = this.shadowRoot.getElementById('visual-editor');
// 清空可视化编辑器
if (visualEditor) {
visualEditor.innerHTML = '';
} else {
console.error('找不到visual-editor元素');
return;
}
if (!this.idlContent || this.idlContent.trim() === '') {
visualEditor.innerHTML += '<div class="empty-message">无内容可显示</div>';
return;
}
try {
// 预处理阶段使用更精确的正则表达式,避免破坏嵌套结构
let processedContent = this.idlContent
.replace(/\r\n/g, '\n') // 统一换行符
.replace(/\r/g, '\n') // 兼容不同系统
.replace(/\/\/.*$/gm, '') // 移除注释
.replace(/\s+/g, ' ') // 压缩多余空白字符
.trim();
// 使用递归下降解析器解析IDL结构
const rootModules = this.parseModules(processedContent);
// 创建模块树结构
const moduleTree = document.createElement('div');
moduleTree.className = 'module-tree';
// 递归构建DOM树
this.buildDOMTree(rootModules, moduleTree);
// 添加到可视化编辑器
if (!visualEditor) {
console.error('找不到visual-editor元素');
return;
}
// 检查是否有解析结果
if (rootModules.length === 0) {
const emptyMsg = document.createElement('div');
emptyMsg.className = 'error-message';
emptyMsg.textContent = '未能解析出任何模块结构';
visualEditor.appendChild(emptyMsg);
return;
}
visualEditor.appendChild(moduleTree);
// 添加可视化编辑器的事件监听
this.addIDLVisualEditorListeners();
} catch (error) {
console.error('解析IDL内容时出错:', error);
const visualEditor = this.shadowRoot.getElementById('visual-editor');
if (visualEditor) {
const errorMsg = document.createElement('div');
errorMsg.className = 'error-message';
errorMsg.textContent = `解析IDL内容时出错: ${error.message}`;
visualEditor.appendChild(errorMsg);
}
}
}
addIDLVisualEditorListeners() {
// 添加子模块按钮
this.shadowRoot.querySelectorAll('.add-submodule-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const moduleName = e.target.dataset.module;
this.showModuleDialog(moduleName);
});
});
// 添加结构体按钮
this.shadowRoot.querySelectorAll('.add-struct-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const moduleName = e.target.dataset.module;
this.showStructDialog(moduleName);
});
});
// 编辑模块按钮
this.shadowRoot.querySelectorAll('.edit-module-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const moduleName = e.target.dataset.module;
const moduleNode = e.target.closest('.module-item');
this.showEditModuleDialog(moduleName, moduleNode);
});
});
// 删除模块按钮
this.shadowRoot.querySelectorAll('.delete-module-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const moduleName = e.target.dataset.module;
const moduleNode = e.target.closest('.module-item');
this.showConfirmDialog(
`删除模块`,
`确定要删除模块 "${moduleName}" 吗?这将同时删除其所有子模块和结构体!`,
() => {
moduleNode.remove();
this.updateIdlFromVisual();
}
);
});
});
// 编辑结构体按钮
this.shadowRoot.querySelectorAll('.edit-struct-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const structName = e.target.dataset.struct;
const structNode = e.target.closest('.struct-item');
this.showEditStructDialog(structName, structNode);
});
});
// 删除结构体按钮
this.shadowRoot.querySelectorAll('.delete-struct-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const structName = e.target.dataset.struct;
const structNode = e.target.closest('.struct-item');
this.showConfirmDialog(
`删除结构体`,
`确定要删除结构体 "${structName}" 吗?`,
() => {
structNode.remove();
this.updateIdlFromVisual();
}
);
});
});
// 添加字段按钮
this.shadowRoot.querySelectorAll('.add-field-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const structName = e.target.dataset.struct;
this.showFieldDialog(structName);
});
});
// 编辑字段按钮
this.shadowRoot.querySelectorAll('.edit-field-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const fieldItem = e.target.closest('.field-item');
const annotation = fieldItem.querySelector('.field-annotation').textContent;
const fieldType = fieldItem.querySelector('.field-type').textContent;
const fieldName = fieldItem.querySelector('.field-name').textContent;
const fieldArray = fieldItem.querySelector('.field-array').textContent;
this.showFieldDialog(null, annotation, fieldType, fieldName, fieldArray, fieldItem);
});
});
// 删除字段按钮
this.shadowRoot.querySelectorAll('.delete-field-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const fieldItem = e.target.closest('.field-item');
const fieldName = fieldItem.querySelector('.field-name').textContent;
this.showConfirmDialog(
`删除字段`,
`确定要删除字段 "${fieldName}" 吗?`,
() => {
fieldItem.remove();
this.updateIdlFromVisual();
}
);
});
});
}
showStructDialog(moduleName) {
// 创建结构体添加对话框
const dialog = document.createElement('div');
dialog.className = 'method-dialog';
dialog.innerHTML = `
<div class="dialog-content">
<h3>添加新结构体</h3>
<div class="form-group">
<label>结构体名称:</label>
<input type="text" id="struct-name" placeholder="例如Aerodynamics_input">
</div>
<div class="dialog-actions">
<button id="dialog-cancel">取消</button>
<button id="dialog-save">保存</button>
</div>
</div>
`;
this.shadowRoot.appendChild(dialog);
// 对话框事件
this.shadowRoot.getElementById('dialog-cancel').addEventListener('click', () => {
dialog.remove();
});
this.shadowRoot.getElementById('dialog-save').addEventListener('click', () => {
const structName = this.shadowRoot.getElementById('struct-name').value.trim();
if (structName) {
// 找到对应的模块
const moduleElem = Array.from(this.shadowRoot.querySelectorAll('.module-item')).find(
item => item.querySelector('.module-name').textContent.includes(moduleName)
);
if (moduleElem) {
// 创建新结构体节点
const structNode = document.createElement('div');
structNode.className = 'struct-item';
structNode.innerHTML = `
<div class="struct-header">
<div class="struct-name">结构体: ${structName}</div>
<div class="struct-actions">
<button class="add-field-btn" data-struct="${structName}">添加字段</button>
</div>
</div>
<div class="struct-fields"></div>
`;
moduleElem.querySelector('.module-content').appendChild(structNode);
// 为新添加的按钮绑定事件
structNode.querySelector('.add-field-btn').addEventListener('click', (e) => {
this.showFieldDialog(structName);
});
// 更新IDL文本
this.updateIdlFromVisual();
}
}
dialog.remove();
});
}
showFieldDialog(structName, annotation = '', fieldType = 'double', fieldName = '', fieldArray = '', fieldItem = null) {
// 创建字段编辑对话框
const dialog = document.createElement('div');
dialog.className = 'method-dialog';
// 如果是编辑现有字段则从DOM元素获取准确的当前字段信息
let currentAnnotation = annotation;
let currentFieldType = fieldType;
let currentFieldName = fieldName;
let currentFieldArray = fieldArray;
if (fieldItem) {
currentAnnotation = fieldItem.querySelector('.field-annotation').textContent.trim();
currentFieldType = fieldItem.querySelector('.field-type').textContent.trim();
currentFieldName = fieldItem.querySelector('.field-name').textContent.trim();
currentFieldArray = fieldItem.querySelector('.field-array').textContent.trim();
}
// 确定数组维度和大小
let arrayDimension = 0;
let arraySizes = ['', ''];
if (currentFieldArray) {
// 提取数组维度和大小
const matches = currentFieldArray.match(/\[([^\]]*)\]/g);
if (matches) {
arrayDimension = matches.length;
for (let i = 0; i < arrayDimension && i < 2; i++) {
arraySizes[i] = matches[i].replace('[', '').replace(']', '');
}
}
}
dialog.innerHTML = `
<div class="dialog-content">
<h3>${fieldItem ? '编辑字段' : '添加新字段'}</h3>
<div class="form-group">
<div class="checkbox-container">
<input type="checkbox" id="field-optional" ${currentAnnotation === '@optional' ? 'checked' : ''}>
<label for="field-optional">可选变量</label>
</div>
</div>
<div class="form-group">
<label>类型:</label>
<select id="field-type">
<option value="char" ${currentFieldType === 'char' ? 'selected' : ''}>char</option>
<option value="octet" ${currentFieldType === 'octet' ? 'selected' : ''}>octet</option>
<option value="short" ${currentFieldType === 'short' ? 'selected' : ''}>short</option>
<option value="unsigned short" ${currentFieldType === 'unsigned short' ? 'selected' : ''}>unsigned short</option>
<option value="long" ${currentFieldType === 'long' ? 'selected' : ''}>long</option>
<option value="unsigned long" ${currentFieldType === 'unsigned long' ? 'selected' : ''}>unsigned long</option>
<option value="long long" ${currentFieldType === 'long long' ? 'selected' : ''}>long long</option>
<option value="unsigned long long" ${currentFieldType === 'unsigned long long' ? 'selected' : ''}>unsigned long long</option>
<option value="float" ${currentFieldType === 'float' ? 'selected' : ''}>float</option>
<option value="double" ${currentFieldType === 'double' ? 'selected' : ''}>double</option>
<option value="long double" ${currentFieldType === 'long double' ? 'selected' : ''}>long double</option>
<option value="boolean" ${currentFieldType === 'boolean' ? 'selected' : ''}>boolean</option>
</select>
</div>
<div class="form-group">
<label>名称:</label>
<input type="text" id="field-name" value="${currentFieldName}" placeholder="例如l_04_i_aerocomac_alpha_f8">
</div>
<div class="form-group">
<label>数组设置:</label>
<div class="array-setting">
<select id="array-dimension">
<option value="0" ${arrayDimension === 0 ? 'selected' : ''}>非数组</option>
<option value="1" ${arrayDimension === 1 ? 'selected' : ''}>1维数组</option>
<option value="2" ${arrayDimension === 2 ? 'selected' : ''}>2维数组</option>
</select>
<div id="array-size-inputs" class="array-size-container">
<div class="array-size-row ${arrayDimension >= 1 ? '' : 'hidden'}" id="array-size-row-1">
<label>维度1大小:</label>
<input type="text" id="array-size-1" value="${arraySizes[0]}" placeholder="例如: 10">
</div>
<div class="array-size-row ${arrayDimension >= 2 ? '' : 'hidden'}" id="array-size-row-2">
<label>维度2大小:</label>
<input type="text" id="array-size-2" value="${arraySizes[1]}" placeholder="例如: 5">
</div>
</div>
</div>
</div>
<div class="dialog-actions">
<button id="dialog-cancel">取消</button>
<button id="dialog-save">保存</button>
</div>
</div>
`;
this.shadowRoot.appendChild(dialog);
// 数组维度选择事件
this.shadowRoot.getElementById('array-dimension').addEventListener('change', (e) => {
const dimension = parseInt(e.target.value);
// 显示/隐藏数组大小输入框
for (let i = 1; i <= 2; i++) {
const row = this.shadowRoot.getElementById(`array-size-row-${i}`);
if (row) {
if (i <= dimension) {
row.classList.remove('hidden');
} else {
row.classList.add('hidden');
}
}
}
});
// 对话框取消事件
this.shadowRoot.getElementById('dialog-cancel').addEventListener('click', () => {
dialog.remove();
});
// 对话框保存事件
this.shadowRoot.getElementById('dialog-save').addEventListener('click', () => {
// 获取字段信息
const isOptional = this.shadowRoot.getElementById('field-optional').checked;
const fieldAnnotation = isOptional ? '@optional' : '';
const fieldType = this.shadowRoot.getElementById('field-type').value;
const fieldName = this.shadowRoot.getElementById('field-name').value.trim();
const arrayDimension = parseInt(this.shadowRoot.getElementById('array-dimension').value);
// 构建数组表示
let fieldArray = '';
if (arrayDimension > 0) {
for (let i = 1; i <= arrayDimension; i++) {
const size = this.shadowRoot.getElementById(`array-size-${i}`).value.trim();
fieldArray += `[${size}]`;
}
}
if (fieldName) {
const fieldContent = `
<div class="field-content">
<span class="field-annotation">${fieldAnnotation}</span>
<span class="field-type">${fieldType}</span>
<span class="field-name">${fieldName}</span>
<span class="field-array">${fieldArray}</span>
</div>
<div class="field-actions">
<button class="edit-field-btn icon-btn" title="编辑字段">
<img src="assets/icons/png/sliders_b.png" alt="编辑字段">
</button>
<button class="delete-field-btn icon-btn" title="删除字段">
<img src="assets/icons/png/delete_b.png" alt="删除字段">
</button>
</div>
`;
if (fieldItem) {
// 更新现有字段
fieldItem.innerHTML = fieldContent;
} else {
// 添加新字段
const newFieldItem = document.createElement('div');
newFieldItem.className = 'field-item';
newFieldItem.innerHTML = fieldContent;
// 找到对应的结构体添加字段
const structElem = Array.from(this.shadowRoot.querySelectorAll('.struct-item')).find(
item => item.querySelector('.struct-name').textContent.includes(structName)
);
if (structElem) {
structElem.querySelector('.struct-fields').appendChild(newFieldItem);
// 为新添加的按钮绑定事件
newFieldItem.querySelector('.edit-field-btn').addEventListener('click', (e) => {
const fieldItem = e.target.closest('.field-item');
const annotation = fieldItem.querySelector('.field-annotation').textContent;
const fieldType = fieldItem.querySelector('.field-type').textContent;
const fieldName = fieldItem.querySelector('.field-name').textContent;
const fieldArray = fieldItem.querySelector('.field-array').textContent;
this.showFieldDialog(null, annotation, fieldType, fieldName, fieldArray, fieldItem);
});
newFieldItem.querySelector('.delete-field-btn').addEventListener('click', (e) => {
const fieldItem = e.target.closest('.field-item');
const fieldName = fieldItem.querySelector('.field-name').textContent;
this.showConfirmDialog(
`删除字段`,
`确定要删除字段 "${fieldName}" 吗?`,
() => {
fieldItem.remove();
this.updateIdlFromVisual();
}
);
});
}
}
// 更新IDL文本
this.updateIdlFromVisual();
}
dialog.remove();
});
}
updateIdlFromVisual() {
// 从可视化编辑器更新IDL文本
let idlText = '';
const visualEditor = this.shadowRoot.getElementById('visual-editor');
const moduleTree = visualEditor.querySelector('.module-tree');
if (moduleTree) {
const topModules = moduleTree.querySelectorAll(':scope > .module-item');
for (const moduleNode of topModules) {
idlText += this.generateModuleText(moduleNode, 0);
}
}
// 更新编辑器文本
this.idlContent = idlText;
const editor = this.shadowRoot.getElementById('idl-editor');
if (editor) {
editor.value = idlText;
}
// 设置为已编辑状态
this.setEditedState(true);
}
async saveFile() {
if (!this.filePath) {
this.showConfirmDialog('保存提示', '请先选择或创建一个文件', null);
return;
}
try {
// 从文本编辑器获取内容
this.idlContent = this.shadowRoot.getElementById('idl-editor').value;
// 调用后端API保存文件
const response = await fetch('/api/idl/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
filename: this.filePath,
content: this.idlContent
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || '保存文件失败');
}
// 显示临时成功消息,不需要确认
this.showTemporaryMessage('文件已成功保存');
// 更新编辑状态
this.setEditedState(false);
} catch (error) {
console.error('保存文件时出错:', error);
this.showConfirmDialog('保存失败', '保存文件时出错: ' + error.message, null);
}
}
async saveFileAs() {
try {
const newFilename = prompt('请输入新文件名:', this.filePath || 'new_interface');
if (!newFilename) {
return; // 用户取消了操作
}
// 从文本编辑器获取内容
this.idlContent = this.shadowRoot.getElementById('idl-editor').value;
// 调用后端API保存文件
const response = await fetch('/api/idl/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
filename: newFilename,
content: this.idlContent
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || '另存为文件失败');
}
const data = await response.json();
// 更新文件路径
this.filePath = data.filename;
// 重新加载文件列表
await this.loadIdlFileList();
this.showTemporaryMessage('文件已成功保存为: ' + this.filePath);
// 更新编辑状态
this.setEditedState(false);
} catch (error) {
//console.error('另存为文件时出错:', error);
//this.showConfirmDialog('另存为错误', '另存为文件时出错: ' + error.message, null);
}
}
showConfirmDialog(title, message, confirmCallback) {
const dialog = document.createElement('div');
dialog.className = 'method-dialog';
dialog.innerHTML = `
<div class="dialog-content">
<h3>${title}</h3>
<p class="dialog-message">${message}</p>
<div class="dialog-actions">
<button id="dialog-cancel">取消</button>
<button id="dialog-confirm">确认</button>
</div>
</div>
`;
this.shadowRoot.appendChild(dialog);
// 对话框事件
this.shadowRoot.getElementById('dialog-cancel').addEventListener('click', () => {
dialog.remove();
});
this.shadowRoot.getElementById('dialog-confirm').addEventListener('click', () => {
if (confirmCallback) confirmCallback();
dialog.remove();
});
}
// 添加一个新方法来显示临时消息
showTemporaryMessage(message, type = 'success') {
const toast = document.createElement('div');
toast.className = `toast-message ${type}`;
toast.textContent = message;
this.shadowRoot.appendChild(toast);
// 2秒后自动消失
setTimeout(() => {
toast.classList.add('fade-out');
setTimeout(() => {
toast.remove();
}, 500);
}, 2000);
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
height: 100%;
overflow: auto;
padding: 16px;
box-sizing: border-box;
font-family: Arial, sans-serif;
}
.config-container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 16px;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.file-actions {
display: flex;
gap: 10px;
margin-left: 10px;
}
.file-section {
display: flex;
align-items: center;
margin-bottom: 20px;
gap: 10px;
flex-wrap: wrap;
}
button {
background-color: #7986E7;
color: white;
border: none;
border-radius: 4px;
padding: 8px 12px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
white-space: nowrap;
}
button:hover {
background-color: #5c67b8;
}
#new-file {
background-color: #7986E7;
}
#new-file:hover {
background-color: #5c67b8;
}
#save-button {
background-color: #7986E7;
transition: background-color 0.3s;
}
#save-button:hover {
background-color: #5c67b8;
}
#save-button.edited {
background-color: #f44336;
}
#save-button.edited:hover {
background-color: #d32f2f;
}
#save-as-button {
background-color: #7986E7;
}
#save-as-button:hover {
background-color: #5c67b8;
}
.file-select-container {
flex: 1;
min-width: 200px;
}
#file-select-dropdown {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: Arial, sans-serif;
font-size: 14px;
background-color: #f5f5f5;
box-sizing: border-box;
}
#editor-container {
display: none;
flex: 1;
min-height: 500px;
overflow: hidden;
}
.editor-layout {
display: flex;
height: 100%;
width: 100%;
gap: 16px;
min-height: 500px;
}
.editor-column {
flex: 1;
width: 100%;
max-width: calc(50% - 8px);
display: flex;
flex-direction: column;
height: 100%;
min-height: 480px;
overflow: hidden;
}
.column-header {
font-weight: bold;
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 1px solid #e0e0e0;
}
#idl-editor {
flex: 1;
width: 100%;
resize: none;
border: 1px solid #ddd;
border-radius: 4px;
padding: 12px;
font-family: monospace;
font-size: 14px;
line-height: 1.5;
white-space: pre;
overflow: auto;
box-sizing: border-box;
min-width: 400px;
}
#visual-editor {
flex: 1;
width: 100%;
overflow: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 12px;
background-color: #fafafa;
box-sizing: border-box;
min-width: 400px;
}
.module-tree {
margin-bottom: 16px;
}
.module-item {
margin-bottom: 16px;
border: 1px solid #e0e0e0;
border-radius: 4px;
overflow: hidden;
background-color: white;
}
.module-header {
background-color: #e3f2fd;
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.module-name {
font-weight: bold;
color: #1565c0;
}
.module-content {
padding: 10px;
}
.struct-item {
margin-bottom: 12px;
border: 1px solid #e0e0e0;
border-radius: 4px;
overflow: hidden;
background-color: white;
}
.struct-header {
background-color: #f5f5f5;
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.struct-name {
font-weight: bold;
color: #555;
}
.struct-fields {
padding: 10px;
}
.field-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
border-bottom: 1px solid #f0f0f0;
background-color: white;
}
.field-item:last-child {
border-bottom: none;
}
.field-content {
display: flex;
align-items: center;
gap: 8px;
}
.field-annotation {
color: #9c27b0;
font-weight: bold;
}
.field-type {
color: #2196f3;
}
.field-name {
color: #333;
}
.field-array {
color: #ff9800;
}
.field-actions {
display: flex;
gap: 8px;
}
.field-actions button {
padding: 4px 8px;
font-size: 12px;
}
.edit-field-btn {
background-color: #ffc107;
color: #333;
}
.delete-field-btn {
background-color: #f44336;
}
.array-setting {
display: flex;
flex-direction: column;
gap: 10px;
}
.array-setting select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: Arial, sans-serif;
box-sizing: border-box;
}
.array-size-container {
display: flex;
flex-direction: column;
gap: 10px;
padding-left: 10px;
margin-top: 5px;
}
.array-size-row {
display: flex;
flex-direction: column;
gap: 5px;
}
.array-size-row label {
font-size: 13px;
color: #555;
}
.array-size-row input {
width: 100%;
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
box-sizing: border-box;
}
.hidden {
display: none;
}
.method-dialog {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.dialog-content {
background-color: white;
border-radius: 8px;
padding: 20px;
width: 400px;
max-width: 90%;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input, .form-group select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
box-sizing: border-box;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
.dialog-message {
margin: 15px 0;
color: #333;
}
#dialog-confirm {
background-color: #f44336;
}
#dialog-confirm:hover {
background-color: #d32f2f;
}
/* 调试样式 */
.debug-info {
margin-top: 0;
margin-bottom: 10px;
padding: 8px;
background-color: #f5f5f5;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
font-size: 12px;
color: #666;
}
.empty-message {
padding: 20px;
text-align: center;
color: #666;
font-style: italic;
background-color: #f9f9f9;
border-radius: 4px;
border: 1px dashed #ddd;
margin-top: 10px;
}
.error-message {
padding: 12px;
background-color: #ffebee;
color: #c62828;
border-radius: 4px;
border: 1px solid #ffcdd2;
margin-top: 10px;
}
.module-actions {
display: flex;
gap: 8px;
}
.struct-actions {
display: flex;
gap: 8px;
}
.add-submodule-btn {
background-color: #2196f3;
}
.add-submodule-btn:hover {
background-color: #1976d2;
}
.add-struct-btn {
background-color: #009688;
}
.add-struct-btn:hover {
background-color: #00796b;
}
.delete-module-btn, .delete-struct-btn {
background-color: #f44336;
}
.delete-module-btn:hover, .delete-struct-btn:hover {
background-color: #d32f2f;
}
.edit-module-btn, .edit-struct-btn {
background-color: #ff9800;
color: #333;
}
.edit-module-btn:hover, .edit-struct-btn:hover {
background-color: #f57c00;
}
/* 文件选择器样式 */
.file-selector-dialog {
width: 500px;
max-width: 90%;
}
.file-list {
max-height: 300px;
overflow-y: auto;
margin: 15px 0;
border: 1px solid #ddd;
border-radius: 4px;
}
.file-item {
padding: 10px;
border-bottom: 1px solid #eee;
cursor: pointer;
transition: background-color 0.2s;
}
.file-item:last-child {
border-bottom: none;
}
.file-item:hover {
background-color: #f5f5f5;
}
.file-name {
font-weight: bold;
display: block;
margin-bottom: 5px;
color: #333;
}
.file-info {
font-size: 12px;
color: #777;
}
/* 图标按钮样式 */
.icon-btn {
background: none;
border: none;
padding: 4px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
transition: transform 0.2s;
border-radius: 4px;
}
.icon-btn:hover {
background-color: rgba(0, 0, 0, 0.05);
transform: scale(1.1);
}
.icon-btn img {
width: 20px;
height: 20px;
display: block;
}
.field-actions .icon-btn img {
width: 16px;
height: 16px;
}
/* 模块和结构体操作区域样式 */
.module-actions, .struct-actions {
display: flex;
gap: 4px;
}
/* 字段编辑对话框样式 */
.checkbox-container {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 5px;
}
.checkbox-container input[type="checkbox"] {
margin: 0;
width: auto;
}
/* 添加Toast消息样式 */
.toast-message {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
padding: 10px 20px;
border-radius: 4px;
font-size: 14px;
font-weight: bold;
color: white;
z-index: 2000;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
opacity: 1;
transition: opacity 0.5s ease;
}
.toast-message.success {
background-color: #4caf50;
}
.toast-message.error {
background-color: #f44336;
}
.toast-message.warning {
background-color: #ff9800;
}
.toast-message.info {
background-color: #2196f3;
}
.toast-message.fade-out {
opacity: 0;
}
</style>
<div class="config-container">
<div class="file-section">
<button id="new-file">新建IDL文件</button>
<div class="file-select-container">
<select id="file-select-dropdown">
<option value="">-- 选择IDL文件 --</option>
</select>
</div>
<div class="file-actions">
<button id="save-button" style="display: none;">保存</button>
<button id="save-as-button" style="display: none;">另存为</button>
</div>
</div>
<div id="editor-container">
<div class="editor-layout">
<div class="editor-column">
<div class="column-header">文本编辑</div>
<textarea id="idl-editor"></textarea>
</div>
<div class="editor-column">
<div class="column-header">可视化编辑</div>
<div id="visual-editor"></div>
</div>
</div>
</div>
</div>
`;
}
// 添加编辑状态设置方法
setEditedState(isEdited) {
this.isEdited = isEdited;
// 更新保存按钮的样式
const saveButton = this.shadowRoot.getElementById('save-button');
if (saveButton) {
if (isEdited) {
saveButton.classList.add('edited');
} else {
saveButton.classList.remove('edited');
}
}
}
// 添加处理文件切换的方法
async handleFileChange(newFilename) {
if (this.isEdited) {
// 显示保存确认对话框
this.showSaveConfirmDialog(
'文件已修改',
'当前文件已修改但未保存,是否保存更改?',
async () => {
// 保存当前文件
await this.saveFile();
// 加载新文件
await this.loadFile(newFilename);
},
async () => {
// 不保存,直接加载新文件
this.setEditedState(false);
await this.loadFile(newFilename);
}
);
} else {
// 直接加载新文件
await this.loadFile(newFilename);
}
}
// 添加处理新建文件的方法
async handleNewFile() {
if (this.isEdited) {
// 显示保存确认对话框
this.showSaveConfirmDialog(
'文件已修改',
'当前文件已修改但未保存,是否保存更改?',
async () => {
// 保存当前文件
await this.saveFile();
// 创建新文件
await this.createNewFile();
},
async () => {
// 不保存,直接创建新文件
this.setEditedState(false);
await this.createNewFile();
}
);
} else {
// 直接创建新文件
await this.createNewFile();
}
}
// 添加保存确认对话框方法
showSaveConfirmDialog(title, message, saveCallback, discardCallback) {
const dialog = document.createElement('div');
dialog.className = 'method-dialog';
dialog.innerHTML = `
<div class="dialog-content">
<h3>${title}</h3>
<p class="dialog-message">${message}</p>
<div class="dialog-actions">
<button id="dialog-cancel">取消</button>
<button id="dialog-discard">不保存</button>
<button id="dialog-save">保存</button>
</div>
</div>
`;
this.shadowRoot.appendChild(dialog);
// 对话框事件
this.shadowRoot.getElementById('dialog-cancel').addEventListener('click', () => {
dialog.remove();
});
this.shadowRoot.getElementById('dialog-save').addEventListener('click', () => {
dialog.remove();
if (saveCallback) saveCallback();
});
this.shadowRoot.getElementById('dialog-discard').addEventListener('click', () => {
dialog.remove();
if (discardCallback) discardCallback();
});
}
// 添加新建IDL文件方法
async createNewFile() {
try {
const filename = prompt('请输入新文件名:', 'new_interface');
if (!filename) {
return; // 用户取消了操作
}
// 调用后端API创建文件
const response = await fetch('/api/idl/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ filename })
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || '创建文件失败');
}
const data = await response.json();
// 更新前端状态
this.fileHandle = null; // 清除本地文件句柄改为使用后端API
this.filePath = data.filename;
this.idlContent = data.content;
// 重新加载文件列表并选择当前文件
await this.loadIdlFileList();
// 设置下拉框选择
const dropdown = this.shadowRoot.getElementById('file-select-dropdown');
if (dropdown) {
dropdown.value = this.filePath;
}
// 显示编辑区域和按钮
this.shadowRoot.getElementById('editor-container').style.display = 'flex';
this.shadowRoot.getElementById('save-button').style.display = 'inline-block';
this.shadowRoot.getElementById('save-as-button').style.display = 'inline-block';
// 更新编辑器内容
const editor = this.shadowRoot.getElementById('idl-editor');
if (editor) {
editor.value = this.idlContent;
}
// 将IDL内容解析为结构化数据并渲染到编辑界面
this.parseIdlContent();
// 重置编辑状态
this.setEditedState(false);
} catch (error) {
//console.error('创建文件时出错:', error);
//this.showConfirmDialog('创建文件错误', '创建文件时出错: ' + error.message, null);
}
}
// HTML转义函数防止XSS
escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
// 新增方法:递归解析模块
parseModules(content) {
const modules = [];
let pos = 0;
while (pos < content.length) {
// 跳过空白字符
while (pos < content.length && /\s/.test(content[pos])) pos++;
if (pos >= content.length) break;
// 检查是否是模块声明
if (content.substring(pos, pos+7) === 'module ') {
// 提取模块名称
pos += 7; // 跳过 'module '
const nameStart = pos;
// 找到模块名称的结束位置(空格或大括号前)
while (pos < content.length && !/[\s{]/.test(content[pos])) pos++;
const moduleName = content.substring(nameStart, pos).trim();
// 跳过空白字符,查找开始大括号
while (pos < content.length && content[pos] !== '{') pos++;
if (pos >= content.length) break;
// 找到开始大括号,现在查找匹配的结束大括号
pos++; // 跳过 '{'
let braceCount = 1;
const moduleContentStart = pos;
while (pos < content.length && braceCount > 0) {
if (content[pos] === '{') braceCount++;
else if (content[pos] === '}') braceCount--;
pos++;
}
if (braceCount === 0) {
// 提取模块内容,不包括最后的大括号
const moduleContent = content.substring(moduleContentStart, pos-1).trim();
// 递归解析子模块和结构体
const module = {
type: 'module',
name: moduleName,
subModules: this.parseModules(moduleContent),
structs: this.parseStructs(moduleContent)
};
modules.push(module);
}
} else {
// 如果不是模块声明,跳过当前字符
pos++;
}
}
return modules;
}
// 新增方法:解析结构体
parseStructs(content) {
const structs = [];
let pos = 0;
while (pos < content.length) {
// 跳过空白字符
while (pos < content.length && /\s/.test(content[pos])) pos++;
if (pos >= content.length) break;
// 检查是否是结构体声明
if (content.substring(pos, pos+7) === 'struct ') {
// 提取结构体名称
pos += 7; // 跳过 'struct '
const nameStart = pos;
// 找到结构体名称的结束位置(空格或大括号前)
while (pos < content.length && !/[\s{]/.test(content[pos])) pos++;
const structName = content.substring(nameStart, pos).trim();
// 跳过空白字符,查找开始大括号
while (pos < content.length && content[pos] !== '{') pos++;
if (pos >= content.length) break;
// 找到开始大括号,现在查找匹配的结束大括号
pos++; // 跳过 '{'
let braceCount = 1;
const structContentStart = pos;
while (pos < content.length && braceCount > 0) {
if (content[pos] === '{') braceCount++;
else if (content[pos] === '}') braceCount--;
pos++;
}
if (braceCount === 0) {
// 提取结构体内容,不包括最后的大括号
const structContent = content.substring(structContentStart, pos-1).trim();
// 解析字段
const fields = this.parseFields(structContent);
const struct = {
type: 'struct',
name: structName,
fields: fields
};
structs.push(struct);
}
} else if (content.substring(pos, pos+7) === 'module ') {
// 跳过模块声明因为它们已经在parseModules中处理了
// 找到匹配的大括号
while (pos < content.length && content[pos] !== '{') pos++;
if (pos >= content.length) break;
pos++; // 跳过 '{'
let braceCount = 1;
while (pos < content.length && braceCount > 0) {
if (content[pos] === '{') braceCount++;
else if (content[pos] === '}') braceCount--;
pos++;
}
} else {
// 如果不是结构体或模块声明,跳过当前字符
pos++;
}
}
return structs;
}
// 新增方法:解析字段
parseFields(content) {
const fields = [];
const lines = content.split(';');
for (let line of lines) {
line = line.trim();
if (!line) continue;
let annotation = '';
let fieldType = '';
let fieldName = '';
let arrayInfo = '';
// 提取注解
if (line.startsWith('@')) {
const annotationEnd = line.indexOf(' ');
if (annotationEnd > 0) {
annotation = line.substring(0, annotationEnd);
line = line.substring(annotationEnd + 1).trim();
}
}
// 提取类型和名称
const parts = line.split(' ').filter(p => p.trim() !== '');
if (parts.length >= 2) {
fieldType = parts[0];
const fullName = parts[1];
// 检查是否是数组
if (fullName.includes('[')) {
fieldName = fullName.substring(0, fullName.indexOf('['));
arrayInfo = fullName.substring(fullName.indexOf('['));
} else {
fieldName = fullName;
}
fields.push({
annotation: annotation,
type: fieldType,
name: fieldName,
array: arrayInfo
});
}
}
return fields;
}
// 新增方法构建DOM树
buildDOMTree(modules, parentElement, isTopLevel = true) {
for (const module of modules) {
// 创建模块节点
const moduleNode = document.createElement('div');
moduleNode.className = 'module-item';
const moduleHeader = document.createElement('div');
moduleHeader.className = 'module-header';
moduleHeader.innerHTML = `
<div class="module-name">模块: ${module.name}</div>
<div class="module-actions">
<button class="add-submodule-btn icon-btn" data-module="${module.name}" title="添加子模块">
<img src="assets/icons/png/addmdl_b.png" alt="添加子模块">
</button>
<button class="add-struct-btn icon-btn" data-module="${module.name}" title="添加结构体">
<img src="assets/icons/png/addstr_b.png" alt="添加结构体">
</button>
${!isTopLevel ? `<button class="edit-module-btn icon-btn" data-module="${module.name}" title="编辑模块">
<img src="assets/icons/png/sliders_b.png" alt="编辑模块">
</button>` : ''}
${!isTopLevel ? `<button class="delete-module-btn icon-btn" data-module="${module.name}" title="删除模块">
<img src="assets/icons/png/delete_b.png" alt="删除模块">
</button>` : ''}
</div>
`;
const moduleContent = document.createElement('div');
moduleContent.className = 'module-content';
moduleNode.appendChild(moduleHeader);
moduleNode.appendChild(moduleContent);
// 递归处理子模块
this.buildDOMTree(module.subModules, moduleContent, false);
// 处理结构体
for (const struct of module.structs) {
const structNode = this.createStructNode(struct);
moduleContent.appendChild(structNode);
}
// 添加到父元素
parentElement.appendChild(moduleNode);
}
}
// 新增方法:创建结构体节点
createStructNode(struct) {
const structNode = document.createElement('div');
structNode.className = 'struct-item';
const structHeader = document.createElement('div');
structHeader.className = 'struct-header';
structHeader.innerHTML = `
<div class="struct-name">结构体: ${struct.name}</div>
<div class="struct-actions">
<button class="add-field-btn icon-btn" data-struct="${struct.name}" title="添加字段">
<img src="assets/icons/png/plus_b.png" alt="添加字段">
</button>
<button class="edit-struct-btn icon-btn" data-struct="${struct.name}" title="编辑结构体">
<img src="assets/icons/png/sliders_b.png" alt="编辑结构体">
</button>
<button class="delete-struct-btn icon-btn" data-struct="${struct.name}" title="删除结构体">
<img src="assets/icons/png/delete_b.png" alt="删除结构体">
</button>
</div>
`;
const structFields = document.createElement('div');
structFields.className = 'struct-fields';
// 添加字段
for (const field of struct.fields) {
const fieldItem = document.createElement('div');
fieldItem.className = 'field-item';
fieldItem.innerHTML = `
<div class="field-content">
<span class="field-annotation">${field.annotation}</span>
<span class="field-type">${field.type}</span>
<span class="field-name">${field.name}</span>
<span class="field-array">${field.array}</span>
</div>
<div class="field-actions">
<button class="edit-field-btn icon-btn" title="编辑字段">
<img src="assets/icons/png/sliders_b.png" alt="编辑字段">
</button>
<button class="delete-field-btn icon-btn" title="删除字段">
<img src="assets/icons/png/delete_b.png" alt="删除字段">
</button>
</div>
`;
structFields.appendChild(fieldItem);
}
structNode.appendChild(structHeader);
structNode.appendChild(structFields);
return structNode;
}
// 新增方法:显示模块添加对话框
showModuleDialog(parentModuleName) {
// 创建模块添加对话框
const dialog = document.createElement('div');
dialog.className = 'method-dialog';
dialog.innerHTML = `
<div class="dialog-content">
<h3>添加子模块</h3>
<div class="form-group">
<label>父模块:</label>
<input type="text" id="parent-module-name" value="${parentModuleName}" disabled>
</div>
<div class="form-group">
<label>模块名称:</label>
<input type="text" id="module-name" placeholder="例如NewModule">
</div>
<div class="dialog-actions">
<button id="dialog-cancel">取消</button>
<button id="dialog-save">保存</button>
</div>
</div>
`;
this.shadowRoot.appendChild(dialog);
// 对话框事件
this.shadowRoot.getElementById('dialog-cancel').addEventListener('click', () => {
dialog.remove();
});
this.shadowRoot.getElementById('dialog-save').addEventListener('click', () => {
const moduleName = this.shadowRoot.getElementById('module-name').value.trim();
if (moduleName) {
// 找到父模块元素
const parentModuleElement = Array.from(this.shadowRoot.querySelectorAll('.module-item')).find(
item => item.querySelector('.module-name').textContent.includes(parentModuleName)
);
if (parentModuleElement) {
// 创建新模块节点
const moduleNode = document.createElement('div');
moduleNode.className = 'module-item';
const moduleHeader = document.createElement('div');
moduleHeader.className = 'module-header';
moduleHeader.innerHTML = `
<div class="module-name">模块: ${moduleName}</div>
<div class="module-actions">
<button class="add-submodule-btn icon-btn" data-module="${moduleName}" title="添加子模块">
<img src="assets/icons/png/addmdl_b.png" alt="添加子模块">
</button>
<button class="add-struct-btn icon-btn" data-module="${moduleName}" title="添加结构体">
<img src="assets/icons/png/addstr_b.png" alt="添加结构体">
</button>
<button class="edit-module-btn icon-btn" data-module="${moduleName}" title="编辑模块">
<img src="assets/icons/png/sliders_b.png" alt="编辑模块">
</button>
<button class="delete-module-btn icon-btn" data-module="${moduleName}" title="删除模块">
<img src="assets/icons/png/delete_b.png" alt="删除模块">
</button>
</div>
`;
const moduleContent = document.createElement('div');
moduleContent.className = 'module-content';
moduleNode.appendChild(moduleHeader);
moduleNode.appendChild(moduleContent);
// 添加到父模块
parentModuleElement.querySelector('.module-content').appendChild(moduleNode);
// 为新添加的按钮绑定事件
moduleNode.querySelector('.add-submodule-btn').addEventListener('click', (e) => {
this.showModuleDialog(moduleName);
});
moduleNode.querySelector('.add-struct-btn').addEventListener('click', (e) => {
this.showStructDialog(moduleName);
});
moduleNode.querySelector('.edit-module-btn').addEventListener('click', (e) => {
this.showEditModuleDialog(moduleName, moduleNode);
});
moduleNode.querySelector('.delete-module-btn').addEventListener('click', (e) => {
this.showConfirmDialog(
`删除模块`,
`确定要删除模块 "${moduleName}" 吗?这将同时删除其所有子模块和结构体!`,
() => {
moduleNode.remove();
this.updateIdlFromVisual();
}
);
});
// 更新IDL文本
this.updateIdlFromVisual();
}
}
dialog.remove();
});
}
// 助手方法:递归生成模块文本
generateModuleText(moduleNode, indentLevel) {
const indent = ' '.repeat(indentLevel);
const moduleName = moduleNode.querySelector('.module-name').textContent.replace('模块: ', '');
let moduleText = `${indent}module ${moduleName}\n${indent}{\n`;
// 处理子内容:子模块和结构体
const moduleContent = moduleNode.querySelector('.module-content');
// 处理子模块
const subModules = moduleContent.querySelectorAll(':scope > .module-item');
for (const subModule of subModules) {
moduleText += this.generateModuleText(subModule, indentLevel + 1);
}
// 处理结构体
const structs = moduleContent.querySelectorAll(':scope > .struct-item');
for (const struct of structs) {
moduleText += this.generateStructText(struct, indentLevel + 1);
}
moduleText += `${indent}};\n\n`;
return moduleText;
}
// 助手方法:生成结构体文本
generateStructText(structNode, indentLevel) {
const indent = ' '.repeat(indentLevel);
const structName = structNode.querySelector('.struct-name').textContent.replace('结构体: ', '');
let structText = `${indent}struct ${structName}\n${indent}{\n`;
// 处理字段
const fields = structNode.querySelectorAll('.field-item');
for (const field of fields) {
const annotation = field.querySelector('.field-annotation').textContent;
const fieldType = field.querySelector('.field-type').textContent;
const fieldName = field.querySelector('.field-name').textContent;
const fieldArray = field.querySelector('.field-array').textContent;
const annotationText = annotation ? `${annotation} ` : '';
structText += `${indent} ${annotationText}${fieldType} ${fieldName}${fieldArray};\n`;
}
structText += `${indent}};\n`;
return structText;
}
// 新增方法:显示模块编辑对话框
showEditModuleDialog(moduleName, moduleNode) {
// 创建模块编辑对话框
const dialog = document.createElement('div');
dialog.className = 'method-dialog';
// 从DOM元素中获取准确的当前模块名称
const currentModuleName = moduleNode.querySelector('.module-name').textContent.replace('模块: ', '').trim();
dialog.innerHTML = `
<div class="dialog-content">
<h3>编辑模块名称</h3>
<div class="form-group">
<label>模块名称:</label>
<input type="text" id="module-name" value="${currentModuleName}" placeholder="例如NewModule">
</div>
<div class="dialog-actions">
<button id="dialog-cancel">取消</button>
<button id="dialog-save">保存</button>
</div>
</div>
`;
this.shadowRoot.appendChild(dialog);
// 对话框事件
this.shadowRoot.getElementById('dialog-cancel').addEventListener('click', () => {
dialog.remove();
});
this.shadowRoot.getElementById('dialog-save').addEventListener('click', () => {
const newModuleName = this.shadowRoot.getElementById('module-name').value.trim();
if (newModuleName && newModuleName !== currentModuleName) {
// 更新模块名称
const moduleNameElement = moduleNode.querySelector('.module-name');
moduleNameElement.textContent = `模块: ${newModuleName}`;
// 更新按钮的dataset
moduleNode.querySelectorAll('[data-module]').forEach(el => {
el.dataset.module = newModuleName;
});
// 更新IDL文本
this.updateIdlFromVisual();
}
dialog.remove();
});
}
// 新增方法:显示结构体编辑对话框
showEditStructDialog(structName, structNode) {
// 创建结构体编辑对话框
const dialog = document.createElement('div');
dialog.className = 'method-dialog';
// 从DOM元素中获取准确的当前结构体名称
const currentStructName = structNode.querySelector('.struct-name').textContent.replace('结构体: ', '').trim();
dialog.innerHTML = `
<div class="dialog-content">
<h3>编辑结构体名称</h3>
<div class="form-group">
<label>结构体名称:</label>
<input type="text" id="struct-name" value="${currentStructName}" placeholder="例如NewStruct">
</div>
<div class="dialog-actions">
<button id="dialog-cancel">取消</button>
<button id="dialog-save">保存</button>
</div>
</div>
`;
this.shadowRoot.appendChild(dialog);
// 对话框事件
this.shadowRoot.getElementById('dialog-cancel').addEventListener('click', () => {
dialog.remove();
});
this.shadowRoot.getElementById('dialog-save').addEventListener('click', () => {
const newStructName = this.shadowRoot.getElementById('struct-name').value.trim();
if (newStructName && newStructName !== currentStructName) {
// 更新结构体名称
const structNameElement = structNode.querySelector('.struct-name');
structNameElement.textContent = `结构体: ${newStructName}`;
// 更新按钮的dataset
structNode.querySelectorAll('[data-struct]').forEach(el => {
el.dataset.struct = newStructName;
});
// 更新IDL文本
this.updateIdlFromVisual();
}
dialog.remove();
});
}
// 添加格式化文件大小的辅助方法
formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
}
// 新增加载文件的方法从后端API获取文件内容
async loadFile(filename) {
try {
const response = await fetch(`/api/idl/read?filename=${encodeURIComponent(filename)}`);
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || '读取文件失败');
}
const data = await response.json();
// 更新前端状态
this.fileHandle = null; // 清除本地文件句柄改为使用后端API
this.filePath = data.filename;
this.idlContent = data.content;
// 更新下拉框选择
const dropdown = this.shadowRoot.getElementById('file-select-dropdown');
if (dropdown) {
dropdown.value = this.filePath;
}
// 显示编辑区域和保存按钮
this.shadowRoot.getElementById('editor-container').style.display = 'flex';
this.shadowRoot.getElementById('save-button').style.display = 'inline-block';
this.shadowRoot.getElementById('save-as-button').style.display = 'inline-block';
// 更新编辑器内容
const editor = this.shadowRoot.getElementById('idl-editor');
if (editor) {
editor.value = this.idlContent;
}
// 将IDL内容解析为结构化数据并渲染到编辑界面
this.parseIdlContent();
// 重置编辑状态
this.setEditedState(false);
} catch (error) {
console.error('加载文件时出错:', error);
this.showConfirmDialog('加载文件错误', '加载文件时出错: ' + error.message, null);
}
}
// 添加加载文件列表的方法
async loadIdlFileList() {
try {
// 获取IDL文件列表
const response = await fetch('/api/idl/list');
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || '获取文件列表失败');
}
const data = await response.json();
// 获取下拉框元素
const dropdown = this.shadowRoot.getElementById('file-select-dropdown');
// 清空现有选项
dropdown.innerHTML = '<option value="">-- 选择IDL文件 --</option>';
// 添加文件选项
if (data.files && data.files.length > 0) {
data.files.forEach(file => {
const option = document.createElement('option');
option.value = file.name;
option.textContent = file.name;
dropdown.appendChild(option);
});
}
} catch (error) {
console.error('加载IDL文件列表失败:', error);
}
}
}
customElements.define('interface-config', InterfaceConfig);