427 lines
18 KiB
JavaScript
427 lines
18 KiB
JavaScript
|
/**
|
|||
|
* 环境配置模块
|
|||
|
* @type {module}
|
|||
|
*/
|
|||
|
import { createModal, markEdited } from './utils.js';
|
|||
|
|
|||
|
/**
|
|||
|
* 渲染环境部分
|
|||
|
* @param {HTMLElement} component - 组件实例
|
|||
|
* @param {HTMLElement} container - 容器元素
|
|||
|
* @param {Element} rootElement - XML根元素
|
|||
|
*/
|
|||
|
export function renderEnvironmentSection(component, container, rootElement) {
|
|||
|
const envElement = rootElement.querySelector('Environment');
|
|||
|
|
|||
|
if (!envElement) {
|
|||
|
return; // 如果找不到Environment元素,直接返回
|
|||
|
}
|
|||
|
|
|||
|
const section = document.createElement('div');
|
|||
|
section.className = 'editor-section';
|
|||
|
|
|||
|
const title = document.createElement('div');
|
|||
|
title.className = 'section-title';
|
|||
|
title.textContent = '环境配置';
|
|||
|
section.appendChild(title);
|
|||
|
|
|||
|
const propertyGroup = document.createElement('div');
|
|||
|
propertyGroup.className = 'property-group';
|
|||
|
|
|||
|
// 获取并显示所有Environment属性
|
|||
|
Array.from(envElement.attributes).forEach(attr => {
|
|||
|
const propertyItem = document.createElement('div');
|
|||
|
propertyItem.className = 'property-item';
|
|||
|
|
|||
|
const label = document.createElement('label');
|
|||
|
label.className = 'property-label';
|
|||
|
label.textContent = attr.name;
|
|||
|
|
|||
|
// 对CPUAffinity属性使用特殊处理
|
|||
|
if (attr.name === 'CPUAffinity') {
|
|||
|
const inputContainer = document.createElement('div');
|
|||
|
inputContainer.style.display = 'flex';
|
|||
|
inputContainer.style.position = 'relative';
|
|||
|
|
|||
|
const input = document.createElement('input');
|
|||
|
input.className = 'property-input';
|
|||
|
input.type = 'text';
|
|||
|
input.value = attr.value;
|
|||
|
input.dataset.path = `Environment@${attr.name}`;
|
|||
|
input.style.flexGrow = '1';
|
|||
|
input.title = 'CPU亲和性';
|
|||
|
input.addEventListener('change', () => {
|
|||
|
component.isEdited = markEdited(component.shadowRoot, component.isEdited);
|
|||
|
});
|
|||
|
|
|||
|
const dropdownButton = document.createElement('button');
|
|||
|
dropdownButton.className = 'dropdown-button';
|
|||
|
dropdownButton.type = 'button'; // 确保按钮类型为button
|
|||
|
dropdownButton.innerHTML = '▼';
|
|||
|
dropdownButton.style.width = '30px';
|
|||
|
dropdownButton.style.border = '1px solid #ddd';
|
|||
|
dropdownButton.style.borderLeft = 'none';
|
|||
|
dropdownButton.style.borderRadius = '0 4px 4px 0';
|
|||
|
dropdownButton.style.backgroundColor = '#f5f5f5';
|
|||
|
dropdownButton.style.cursor = 'pointer';
|
|||
|
|
|||
|
// 创建下拉框容器
|
|||
|
const dropdown = document.createElement('div');
|
|||
|
dropdown.className = 'cpu-dropdown';
|
|||
|
dropdown.style.position = 'absolute';
|
|||
|
dropdown.style.top = '100%';
|
|||
|
dropdown.style.left = '0';
|
|||
|
dropdown.style.right = '0';
|
|||
|
dropdown.style.backgroundColor = 'white';
|
|||
|
dropdown.style.border = '1px solid #ddd';
|
|||
|
dropdown.style.borderRadius = '4px';
|
|||
|
dropdown.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)';
|
|||
|
dropdown.style.zIndex = '50'; // 提高z-index确保在最上层
|
|||
|
dropdown.style.padding = '10px';
|
|||
|
dropdown.style.display = 'none';
|
|||
|
|
|||
|
// 生成CPU核心选项
|
|||
|
const cpuCount = 16; // 假设最多16个核心
|
|||
|
|
|||
|
const selectedCores = attr.value.split(',').map(core => parseInt(core.trim())).filter(core => !isNaN(core));
|
|||
|
|
|||
|
for (let i = 0; i < cpuCount; i++) {
|
|||
|
const checkboxContainer = document.createElement('div');
|
|||
|
checkboxContainer.style.marginBottom = '6px';
|
|||
|
|
|||
|
const checkbox = document.createElement('input');
|
|||
|
checkbox.type = 'checkbox';
|
|||
|
checkbox.id = `cpu-${i}`;
|
|||
|
checkbox.value = i;
|
|||
|
checkbox.checked = selectedCores.includes(i);
|
|||
|
checkbox.style.marginRight = '6px';
|
|||
|
|
|||
|
const checkboxLabel = document.createElement('label');
|
|||
|
checkboxLabel.htmlFor = `cpu-${i}`;
|
|||
|
checkboxLabel.textContent = `CPU ${i}`;
|
|||
|
|
|||
|
checkbox.addEventListener('change', () => {
|
|||
|
// 获取所有选中的核心
|
|||
|
const checkedCores = Array.from(dropdown.querySelectorAll('input[type="checkbox"]:checked'))
|
|||
|
.map(cb => cb.value)
|
|||
|
.sort((a, b) => a - b);
|
|||
|
|
|||
|
// 更新输入框的值
|
|||
|
input.value = checkedCores.join(',');
|
|||
|
input.dispatchEvent(new Event('change'));
|
|||
|
});
|
|||
|
|
|||
|
checkboxContainer.appendChild(checkbox);
|
|||
|
checkboxContainer.appendChild(checkboxLabel);
|
|||
|
dropdown.appendChild(checkboxContainer);
|
|||
|
}
|
|||
|
|
|||
|
// 添加应用和取消按钮
|
|||
|
const buttonContainer = document.createElement('div');
|
|||
|
buttonContainer.style.display = 'flex';
|
|||
|
buttonContainer.style.justifyContent = 'flex-end';
|
|||
|
buttonContainer.style.marginTop = '10px';
|
|||
|
buttonContainer.style.gap = '8px';
|
|||
|
|
|||
|
const closeButton = document.createElement('button');
|
|||
|
closeButton.textContent = '关闭';
|
|||
|
closeButton.style.padding = '6px 12px';
|
|||
|
closeButton.style.backgroundColor = '#f0f0f0';
|
|||
|
closeButton.style.border = '1px solid #ddd';
|
|||
|
closeButton.style.borderRadius = '4px';
|
|||
|
closeButton.style.cursor = 'pointer';
|
|||
|
|
|||
|
closeButton.addEventListener('click', (e) => {
|
|||
|
e.preventDefault();
|
|||
|
e.stopPropagation();
|
|||
|
dropdown.style.display = 'none';
|
|||
|
});
|
|||
|
|
|||
|
buttonContainer.appendChild(closeButton);
|
|||
|
dropdown.appendChild(buttonContainer);
|
|||
|
|
|||
|
// 使用更可靠的方法显示下拉菜单
|
|||
|
const toggleDropdown = (e) => {
|
|||
|
e.preventDefault();
|
|||
|
e.stopPropagation();
|
|||
|
if (dropdown.style.display === 'none' || !dropdown.style.display) {
|
|||
|
dropdown.style.display = 'block';
|
|||
|
// 添加全局一次性点击事件,用于关闭下拉框
|
|||
|
setTimeout(() => {
|
|||
|
const closeDropdown = (event) => {
|
|||
|
if (!dropdown.contains(event.target) && event.target !== dropdownButton) {
|
|||
|
dropdown.style.display = 'none';
|
|||
|
document.removeEventListener('click', closeDropdown);
|
|||
|
}
|
|||
|
};
|
|||
|
document.addEventListener('click', closeDropdown);
|
|||
|
}, 0);
|
|||
|
} else {
|
|||
|
dropdown.style.display = 'none';
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
// 点击下拉按钮时显示或隐藏下拉框
|
|||
|
dropdownButton.addEventListener('click', toggleDropdown);
|
|||
|
|
|||
|
inputContainer.appendChild(input);
|
|||
|
inputContainer.appendChild(dropdownButton);
|
|||
|
inputContainer.appendChild(dropdown);
|
|||
|
|
|||
|
propertyItem.appendChild(label);
|
|||
|
propertyItem.appendChild(inputContainer);
|
|||
|
} else if (attr.name === 'BaseFrequency') {
|
|||
|
// 对数字类型的属性使用number类型输入框
|
|||
|
const input = document.createElement('input');
|
|||
|
input.className = 'property-input';
|
|||
|
input.type = 'number';
|
|||
|
input.min = '0';
|
|||
|
input.value = attr.value;
|
|||
|
input.dataset.path = `Environment@${attr.name}`;
|
|||
|
input.title = '仿真运行基础频率,单位Hz';
|
|||
|
input.addEventListener('change', () => {
|
|||
|
component.isEdited = markEdited(component.shadowRoot, component.isEdited);
|
|||
|
});
|
|||
|
|
|||
|
propertyItem.appendChild(label);
|
|||
|
propertyItem.appendChild(input);
|
|||
|
} else if (attr.name === 'OSName') {
|
|||
|
const input = document.createElement('input');
|
|||
|
input.className = 'property-input';
|
|||
|
input.type = 'text';
|
|||
|
input.value = attr.value;
|
|||
|
input.dataset.path = `Environment@${attr.name}`;
|
|||
|
input.title = '操作系统名称';
|
|||
|
input.addEventListener('change', () => {
|
|||
|
component.isEdited = markEdited(component.shadowRoot, component.isEdited);
|
|||
|
});
|
|||
|
|
|||
|
propertyItem.appendChild(label);
|
|||
|
propertyItem.appendChild(input);
|
|||
|
} else if (attr.name === 'Version') {
|
|||
|
const input = document.createElement('input');
|
|||
|
input.className = 'property-input';
|
|||
|
input.type = 'text';
|
|||
|
input.value = attr.value;
|
|||
|
input.dataset.path = `Environment@${attr.name}`;
|
|||
|
input.title = '操作系统版本';
|
|||
|
input.addEventListener('change', () => {
|
|||
|
component.isEdited = markEdited(component.shadowRoot, component.isEdited);
|
|||
|
});
|
|||
|
|
|||
|
propertyItem.appendChild(label);
|
|||
|
propertyItem.appendChild(input);
|
|||
|
} else if (attr.name === 'RTXVersion') {
|
|||
|
const input = document.createElement('input');
|
|||
|
input.className = 'property-input';
|
|||
|
input.type = 'text';
|
|||
|
input.value = attr.value;
|
|||
|
input.dataset.path = `Environment@${attr.name}`;
|
|||
|
input.title = '实时内核版本';
|
|||
|
input.addEventListener('change', () => {
|
|||
|
component.isEdited = markEdited(component.shadowRoot, component.isEdited);
|
|||
|
});
|
|||
|
|
|||
|
propertyItem.appendChild(label);
|
|||
|
propertyItem.appendChild(input);
|
|||
|
} else if (attr.name === 'WorkPath') {
|
|||
|
const input = document.createElement('input');
|
|||
|
input.className = 'property-input';
|
|||
|
input.type = 'text';
|
|||
|
input.value = attr.value;
|
|||
|
input.dataset.path = `Environment@${attr.name}`;
|
|||
|
input.title = '仿真工作路径,XNCore环境变量所在路径';
|
|||
|
input.addEventListener('change', () => {
|
|||
|
component.isEdited = markEdited(component.shadowRoot, component.isEdited);
|
|||
|
});
|
|||
|
|
|||
|
propertyItem.appendChild(label);
|
|||
|
propertyItem.appendChild(input);
|
|||
|
} else if (attr.name === 'ModelsPath') {
|
|||
|
const input = document.createElement('input');
|
|||
|
input.className = 'property-input';
|
|||
|
input.type = 'text';
|
|||
|
input.value = attr.value;
|
|||
|
input.dataset.path = `Environment@${attr.name}`;
|
|||
|
input.title = '模型相对路径,相对于仿真工作路径';
|
|||
|
input.addEventListener('change', () => {
|
|||
|
component.isEdited = markEdited(component.shadowRoot, component.isEdited);
|
|||
|
});
|
|||
|
|
|||
|
propertyItem.appendChild(label);
|
|||
|
propertyItem.appendChild(input);
|
|||
|
} else if (attr.name === 'ServicesPath') {
|
|||
|
const input = document.createElement('input');
|
|||
|
input.className = 'property-input';
|
|||
|
input.type = 'text';
|
|||
|
input.value = attr.value;
|
|||
|
input.dataset.path = `Environment@${attr.name}`;
|
|||
|
input.title = '服务相对路径,相对于仿真工作路径';
|
|||
|
input.addEventListener('change', () => {
|
|||
|
component.isEdited = markEdited(component.shadowRoot, component.isEdited);
|
|||
|
});
|
|||
|
|
|||
|
propertyItem.appendChild(label);
|
|||
|
propertyItem.appendChild(input);
|
|||
|
} else if (attr.name === 'DomainID') {
|
|||
|
// 对数字类型的属性使用number类型输入框
|
|||
|
const input = document.createElement('input');
|
|||
|
input.className = 'property-input';
|
|||
|
input.type = 'number';
|
|||
|
input.min = '0';
|
|||
|
input.value = attr.value;
|
|||
|
input.dataset.path = `Environment@${attr.name}`;
|
|||
|
input.title = 'DDS通信域ID';
|
|||
|
input.addEventListener('change', () => {
|
|||
|
component.isEdited = markEdited(component.shadowRoot, component.isEdited);
|
|||
|
});
|
|||
|
|
|||
|
propertyItem.appendChild(label);
|
|||
|
propertyItem.appendChild(input);
|
|||
|
} else {
|
|||
|
const input = document.createElement('input');
|
|||
|
input.className = 'property-input';
|
|||
|
input.type = 'text';
|
|||
|
input.value = attr.value;
|
|||
|
input.dataset.path = `Environment@${attr.name}`;
|
|||
|
input.addEventListener('change', () => {
|
|||
|
component.isEdited = markEdited(component.shadowRoot, component.isEdited);
|
|||
|
});
|
|||
|
|
|||
|
propertyItem.appendChild(label);
|
|||
|
propertyItem.appendChild(input);
|
|||
|
}
|
|||
|
|
|||
|
propertyGroup.appendChild(propertyItem);
|
|||
|
});
|
|||
|
|
|||
|
section.appendChild(propertyGroup);
|
|||
|
|
|||
|
// 添加"添加新属性"按钮
|
|||
|
const addButton = document.createElement('button');
|
|||
|
addButton.className = 'add-button';
|
|||
|
addButton.textContent = '添加新属性';
|
|||
|
addButton.title = '添加新的运行环境属性';
|
|||
|
addButton.addEventListener('click', () => showAddPropertyDialog(component, 'Environment'));
|
|||
|
section.appendChild(addButton);
|
|||
|
|
|||
|
container.appendChild(section);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 添加新属性对话框
|
|||
|
* @param {HTMLElement} component - 组件实例
|
|||
|
* @param {string} elementName - 元素名称
|
|||
|
*/
|
|||
|
export function showAddPropertyDialog(component, elementName) {
|
|||
|
// 创建表单内容
|
|||
|
const formContent = document.createElement('div');
|
|||
|
formContent.className = 'modal-form';
|
|||
|
|
|||
|
// 属性名输入
|
|||
|
const nameRow = document.createElement('div');
|
|||
|
nameRow.className = 'form-row';
|
|||
|
|
|||
|
const nameLabel = document.createElement('label');
|
|||
|
nameLabel.className = 'form-label';
|
|||
|
nameLabel.textContent = '属性名称';
|
|||
|
|
|||
|
const nameInput = document.createElement('input');
|
|||
|
nameInput.className = 'form-input';
|
|||
|
nameInput.type = 'text';
|
|||
|
nameInput.placeholder = '请输入属性名称';
|
|||
|
|
|||
|
nameRow.appendChild(nameLabel);
|
|||
|
nameRow.appendChild(nameInput);
|
|||
|
formContent.appendChild(nameRow);
|
|||
|
|
|||
|
// 属性值输入
|
|||
|
const valueRow = document.createElement('div');
|
|||
|
valueRow.className = 'form-row';
|
|||
|
|
|||
|
const valueLabel = document.createElement('label');
|
|||
|
valueLabel.className = 'form-label';
|
|||
|
valueLabel.textContent = '属性值';
|
|||
|
|
|||
|
const valueInput = document.createElement('input');
|
|||
|
valueInput.className = 'form-input';
|
|||
|
valueInput.type = 'text';
|
|||
|
valueInput.placeholder = '请输入属性值';
|
|||
|
|
|||
|
valueRow.appendChild(valueLabel);
|
|||
|
valueRow.appendChild(valueInput);
|
|||
|
formContent.appendChild(valueRow);
|
|||
|
|
|||
|
// 显示对话框
|
|||
|
createModal(component.shadowRoot, '添加新属性', formContent, () => {
|
|||
|
const propertyName = nameInput.value.trim();
|
|||
|
const propertyValue = valueInput.value;
|
|||
|
|
|||
|
if (propertyName) {
|
|||
|
addNewProperty(component, elementName, propertyName, propertyValue);
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 添加新属性
|
|||
|
* @param {HTMLElement} component - 组件实例
|
|||
|
* @param {string} elementName - 元素名称
|
|||
|
* @param {string} propertyName - 属性名
|
|||
|
* @param {string} propertyValue - 属性值
|
|||
|
*/
|
|||
|
export function addNewProperty(component, elementName, propertyName, propertyValue) {
|
|||
|
// 更新UI
|
|||
|
const section = Array.from(component.shadowRoot.querySelectorAll('.section-title')).find(el => {
|
|||
|
if (elementName === 'Environment' && el.textContent === '环境配置') return true;
|
|||
|
return false;
|
|||
|
})?.parentElement;
|
|||
|
|
|||
|
if (section) {
|
|||
|
const propertyGroup = section.querySelector('.property-group');
|
|||
|
if (propertyGroup) {
|
|||
|
const propertyItem = document.createElement('div');
|
|||
|
propertyItem.className = 'property-item';
|
|||
|
|
|||
|
const label = document.createElement('label');
|
|||
|
label.className = 'property-label';
|
|||
|
label.textContent = propertyName;
|
|||
|
|
|||
|
const input = document.createElement('input');
|
|||
|
input.className = 'property-input';
|
|||
|
input.type = 'text';
|
|||
|
input.value = propertyValue;
|
|||
|
input.dataset.path = `${elementName}@${propertyName}`;
|
|||
|
input.dataset.isNew = 'true';
|
|||
|
input.addEventListener('change', () => {
|
|||
|
component.isEdited = markEdited(component.shadowRoot, component.isEdited);
|
|||
|
});
|
|||
|
|
|||
|
propertyItem.appendChild(label);
|
|||
|
propertyItem.appendChild(input);
|
|||
|
propertyGroup.appendChild(propertyItem);
|
|||
|
|
|||
|
component.isEdited = markEdited(component.shadowRoot, component.isEdited);
|
|||
|
|
|||
|
// 静默更新XML
|
|||
|
if (component.updateXmlFromVisualEditor) {
|
|||
|
component.updateXmlFromVisualEditor(true);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取CPU亲和性设置
|
|||
|
* @param {Document} xmlDoc - XML文档
|
|||
|
* @returns {number[]} 可用的CPU核心列表
|
|||
|
*/
|
|||
|
export function getCPUAffinityOptions(xmlDoc) {
|
|||
|
// 从环境配置中获取CPUAffinity的值
|
|||
|
const envElement = xmlDoc.querySelector('Environment');
|
|||
|
if (!envElement) return [];
|
|||
|
|
|||
|
const cpuAffinity = envElement.getAttribute('CPUAffinity') || '0';
|
|||
|
const availableCores = cpuAffinity.split(',').map(core => parseInt(core.trim())).filter(core => !isNaN(core));
|
|||
|
return availableCores;
|
|||
|
}
|