XNSim/XNSimHtml/bak/interface-config.js

2120 lines
81 KiB
JavaScript
Raw Normal View History

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