2120 lines
81 KiB
JavaScript
2120 lines
81 KiB
JavaScript
|
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, "&")
|
|||
|
.replace(/</g, "<")
|
|||
|
.replace(/>/g, ">")
|
|||
|
.replace(/"/g, """)
|
|||
|
.replace(/'/g, "'");
|
|||
|
}
|
|||
|
|
|||
|
// 新增方法:递归解析模块
|
|||
|
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);
|