0
0
Files
vs-p/out/panels/views/ConfigView.js
2026-01-30 11:06:42 +08:00

990 lines
39 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigView = void 0;
const BaseView_1 = require("./BaseView");
class ConfigView extends BaseView_1.BaseView {
render(data) {
const container = data?.container;
const configs = data?.configs || [];
const moduleFolders = data?.moduleFolders || [];
const currentModuleFolder = data?.currentModuleFolder;
const moduleFolderFileTree = data?.moduleFolderFileTree || [];
const moduleFolderLoading = data?.moduleFolderLoading || false;
const gitBranches = data?.gitBranches || [];
// 生成配置列表的 HTML
const configsHtml = configs.map((config) => `
<tr>
<td>
<input type="checkbox" class="config-checkbox" data-id="${config.id}" data-type="config">
<span class="editable"
onclick="editConfigName('${config.id}', '${config.name.replace(/'/g, '\\\'')}', '${config.fileName.replace(/'/g, '\\\'')}')">
🔧 ${config.name}
</span>
</td>
<td>local</td>
<td>
<span class="clickable config-file-name" data-filename="${config.fileName}" onclick="openConfigFileInVSCode('${config.id}')">📄 ${config.fileName}</span>
</td>
<td>
<button class="btn-delete" onclick="deleteConfig('${config.id}')">删除</button>
</td>
</tr>
`).join('');
// 生成模块文件夹的 HTML
const moduleFoldersHtml = moduleFolders.map((folder) => {
const icon = '📁';
let category = folder.type === 'git' ? 'git' : 'local';
if (folder.uploaded) {
category += '(已上传)';
}
// 确保文件名被正确提取和转义
const fileName = folder.localPath.split('/').pop() || '';
const escapedFolderName = folder.name.replace(/'/g, '\\\'');
return `
<tr>
<td>
<span class="editable clickable module-folder-name" data-folder-id="${folder.id}"
onclick="renameModuleFolder('${folder.id}', '${escapedFolderName}')">
${icon} ${folder.name}
</span>
</td>
<td class="category-${folder.type}">${category}</td>
<td>
<span class="clickable module-disk-path" data-disk-name="${fileName}"
onclick="openTheModuleFolder('${folder.id}', '${folder.type}')">
📄 ${fileName}
</span>
</td>
<td>
<button class="btn-upload" onclick="uploadModuleFolder('${folder.id}', '${folder.type}')" style="margin-right: 5px;">上传</button>
<button class="btn-delete" onclick="deleteModuleFolder('${folder.id}')">删除</button>
</td>
</tr>
`;
}).join('');
// 生成分支选择的 HTML
const branchesHtml = gitBranches.length > 0 ? this.generateBranchesTreeHtml(gitBranches) : '';
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模块管理</title>
${this.getBaseStylesAndScripts()}
${this.getRepoSelectScript()}
<style>
.url-input-section {
background: var(--vscode-panel-background);
padding: 15px;
border-radius: 4px;
margin-bottom: 15px;
border: 1px solid var(--vscode-input-border);
}
.section-title {
margin: 30px 0 15px 0;
padding-bottom: 10px;
border-bottom: 2px solid var(--vscode-panel-border);
color: var(--vscode-titleBar-activeForeground);
}
.config-section {
margin-bottom: 30px;
}
.branch-selection {
border: 1px solid var(--vscode-input-border);
}
.branch-item:hover {
background: var(--vscode-list-hoverBackground);
}
.btn-delete {
background: var(--vscode-inputValidation-errorBackground);
color: white;
padding: 4px 8px;
border: none;
border-radius: 2px;
cursor: pointer;
}
.btn-delete:hover {
background: var(--vscode-inputValidation-errorBackground);
opacity: 0.8;
}
.btn-upload {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
padding: 4px 8px;
border: none;
border-radius: 2px;
cursor: pointer;
}
.btn-upload:hover {
background: var(--vscode-button-hoverBackground);
}
.btn-upload:disabled {
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
cursor: not-allowed;
}
/* 树状分支样式 */
.branch-tree {
font-family: 'Courier New', monospace;
user-select: none;
margin: 0;
padding: 0;
}
.branch-node {
padding: 2px 0;
display: flex;
align-items: center;
cursor: pointer;
line-height: 1.2;
}
.branch-node:hover {
background: var(--vscode-list-hoverBackground);
}
.branch-icon {
margin-right: 4px;
font-size: 16px;
width: 16px;
text-align: center;
}
.branch-expand {
margin-right: 2px;
cursor: pointer;
width: 14px;
text-align: center;
}
.branch-checkbox {
margin-right: 6px;
}
.branch-name {
flex: 1;
font-size: 13px;
}
.branch-children {
margin-left: 16px;
border-left: 1px solid var(--vscode-panel-border);
padding-left: 8px;
}
.branch-leaf {
margin-left: 16px;
}
.current-branch {
color: var(--vscode-gitDecoration-untrackedResourceForeground);
font-weight: bold;
}
/* 树图标样式 */
.tree-icon {
font-size: 2em;
}
/* 分支树标题样式 */
.branch-tree-title {
font-size: 1.2em;
}
/* 复选框样式 */
.config-checkbox {
margin-right: 8px;
}
/* 操作按钮样式 */
.action-buttons {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.btn-merge {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
.btn-merge:hover {
background: var(--vscode-button-hoverBackground);
}
.btn-merge:disabled {
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
cursor: not-allowed;
}
/* 合并弹窗样式 */
.merge-dialog-content {
margin: 15px 0;
}
.merge-input-section {
margin-bottom: 15px;
}
.merge-input-label {
font-weight: bold;
margin-bottom: 8px;
display: block;
}
.merge-input-field {
width: 100%;
padding: 8px;
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: 1px solid var(--vscode-input-border);
border-radius: 4px;
margin-bottom: 5px;
}
.merge-input-help {
font-size: 12px;
color: var(--vscode-descriptionForeground);
}
/* 类别标签样式 */
.category-git {
color: var(--vscode-gitDecoration-untrackedResourceForeground);
font-weight: bold;
}
.category-local {
color: var(--vscode-charts-orange);
font-weight: bold;
}
</style>
</head>
<body>
<div class="header">
<h2>⚙️ 模块管理 - <span style="color: var(--vscode-textLink-foreground);">${container?.name || '未知容器'}</span></h2>
<button class="back-btn" onclick="goBackToContainers()">← 返回容器管理</button>
</div>
<div class="config-section">
<h3 class="section-title">📋 配置文件管理</h3>
<div class="action-buttons">
<button class="btn-new" onclick="createNewConfig()">+ 新建配置</button>
<button class="btn-merge" id="mergeButton" onclick="mergeSelectedConfigs()" disabled>合并选中配置</button>
</div>
<table class="table">
<thead>
<tr>
<th width="25%">模块</th>
<th width="15%">类别</th>
<th width="35%">文件/文件夹</th>
<th width="25%">操作</th>
</tr>
</thead>
<tbody>
${configsHtml}
${moduleFoldersHtml}
</tbody>
</table>
</div>
<div class="config-section">
<h3 class="section-title">📚 模块云仓库</h3>
<div class="url-input-section">
<h4>🔗 获取仓库</h4>
<div style="display: flex; gap: 10px; margin-top: 10px; align-items: center;">
<button class="btn-new" onclick="openRepoSelect()">获取仓库</button>
<span style="font-size: 12px; color: var(--vscode-descriptionForeground);">
从仓库配置中选择 Git 仓库,随后可以选择分支并克隆到本地
</span>
</div>
<div id="branchSelectionContainer">
${branchesHtml}
</div>
</div>
</div>
<script>
const vscode = acquireVsCodeApi();
let selectedBranches = new Set();
let branchTreeData = [];
let selectedConfigs = new Set();
// [新增] 配置文件重命名弹窗
function showConfigRenameDialog(configId, currentName, currentFileName) {
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
overlay.id = 'configRenameModal';
overlay.innerHTML = \`
<div class="modal-dialog" style="width: 450px;">
<div class="modal-title">重命名配置文件</div>
<div class="merge-dialog-content">
<div class="merge-input-section">
<label class="merge-input-label">配置显示名称</label>
<input type="text" id="configDisplayNameInput" class="merge-input-field"
placeholder="在配置栏显示的名称" value="\${currentName}">
<div class="merge-input-help">在左侧配置栏显示的名称</div>
</div>
<div class="merge-input-section">
<label class="merge-input-label">文件名称 (将同步修改磁盘文件)</label>
<input type="text" id="configFileNameInput" class="merge-input-field"
placeholder="磁盘上的实际文件名" value="\${currentFileName}">
<div class="merge-input-help">请使用英文、数字、中文、-、_不要使用空格和/</div>
</div>
<div id="fileNameValidationMessage" style="color: var(--vscode-inputValidation-errorForeground); font-size: 12px; margin-top: 5px;"></div>
</div>
<div class="modal-buttons">
<button class="modal-btn modal-btn-secondary" onclick="closeConfigRenameDialog()">取消</button>
<button class="modal-btn modal-btn-primary" id="configRenameConfirmBtn">确定</button>
</div>
</div>
\`;
document.body.appendChild(overlay);
const displayNameInput = document.getElementById('configDisplayNameInput');
const fileNameInput = document.getElementById('configFileNameInput');
const confirmBtn = document.getElementById('configRenameConfirmBtn');
const validationMessage = document.getElementById('fileNameValidationMessage');
function validateFileName(input) {
// 允许中文、字母、数字、-、_
if (!input.trim()) {
validationMessage.textContent = '文件名不能为空';
confirmBtn.disabled = true;
return false;
}
if (input.includes('/') || input.includes(' ')) {
validationMessage.textContent = '文件名不能包含空格和/,请使用-或_代替';
confirmBtn.disabled = true;
return false;
}
validationMessage.textContent = '';
confirmBtn.disabled = false;
return true;
}
if (fileNameInput) {
fileNameInput.addEventListener('input', (e) => {
validateFileName(e.target.value);
});
validateFileName(fileNameInput.value);
}
confirmBtn.addEventListener('click', function() {
const newName = displayNameInput.value.trim();
const newFileName = fileNameInput.value.trim();
if (!validateFileName(newFileName)) {
alert(validationMessage.textContent || '请修复文件名错误');
return;
}
if (newName && newFileName && (newName !== currentName || newFileName !== currentFileName)) {
vscode.postMessage({
type: 'updateConfigName',
configId: configId,
name: newName,
fileName: newFileName
});
}
closeConfigRenameDialog();
});
setTimeout(() => {
if (displayNameInput) {
displayNameInput.focus();
displayNameInput.select();
}
}, 100);
}
function closeConfigRenameDialog() {
const modal = document.getElementById('configRenameModal');
if (modal) {
modal.remove();
}
}
// [修改] editConfigName使用新的双输入弹窗
function editConfigName(configId, currentName, currentFileName) {
// currentFileName 现在已从模板传入
showConfigRenameDialog(configId, currentName, currentFileName);
}
// [新增] 模块文件夹重命名双输入弹窗
function showModuleFolderRenameDialog(folderId, currentName, currentFolderName) {
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
overlay.id = 'moduleRenameModal';
overlay.innerHTML = \`
<div class="modal-dialog" style="width: 450px;">
<div class="modal-title">重命名模块文件夹</div>
<div class="merge-dialog-content">
<div class="merge-input-section">
<label class="merge-input-label">显示名称</label>
<input type="text" id="moduleDisplayNameInput" class="merge-input-field"
placeholder="在配置栏显示的名称" value="\${currentName}">
<div class="merge-input-help">在左侧配置栏显示的名称</div>
</div>
<div class="merge-input-section">
<label class="merge-input-label">磁盘文件夹名称 (将同步修改磁盘目录)</label>
<input type="text" id="moduleFolderNameInput" class="merge-input-field"
placeholder="磁盘上的实际文件夹名" value="\${currentFolderName}">
<div class="merge-input-help">请使用英文、数字、中文、-、_不要使用空格和/</div>
</div>
<div id="moduleFolderNameValidationMessage" style="color: var(--vscode-inputValidation-errorForeground); font-size: 12px; margin-top: 5px;"></div>
</div>
<div class="modal-buttons">
<button class="modal-btn modal-btn-secondary" onclick="closeModuleFolderRenameDialog()">取消</button>
<button class="modal-btn modal-btn-primary" id="moduleRenameConfirmBtn">确定</button>
</div>
</div>
\`;
document.body.appendChild(overlay);
const displayNameInput = document.getElementById('moduleDisplayNameInput');
const folderNameInput = document.getElementById('moduleFolderNameInput');
const confirmBtn = document.getElementById('moduleRenameConfirmBtn');
const validationMessage = document.getElementById('moduleFolderNameValidationMessage');
function validateFolderName(input) {
// 允许中文、字母、数字、-、_
if (!input.trim()) {
validationMessage.textContent = '文件夹名不能为空';
confirmBtn.disabled = true;
return false;
}
if (input.includes('/') || input.includes(' ')) {
validationMessage.textContent = '文件夹名不能包含空格和/,请使用-或_代替';
confirmBtn.disabled = true;
return false;
}
validationMessage.textContent = '';
confirmBtn.disabled = false;
return true;
}
if (folderNameInput) {
folderNameInput.addEventListener('input', (e) => {
validateFolderName(e.target.value);
});
validateFolderName(folderNameInput.value);
}
confirmBtn.addEventListener('click', function() {
const newName = displayNameInput.value.trim();
const newFolderName = folderNameInput.value.trim();
if (!validateFolderName(newFolderName)) {
alert(validationMessage.textContent || '请修复文件夹名错误');
return;
}
if (newName && newFolderName && (newName !== currentName || newFolderName !== currentFolderName)) {
vscode.postMessage({
type: 'renameModuleFolder',
folderId: folderId,
newName: newName,
newFolderName: newFolderName
});
}
closeModuleFolderRenameDialog();
});
setTimeout(() => {
if (displayNameInput) {
displayNameInput.focus();
displayNameInput.select();
}
}, 100);
}
function closeModuleFolderRenameDialog() {
const modal = document.getElementById('moduleRenameModal');
if (modal) {
modal.remove();
}
}
// [修改] renameModuleFolder获取磁盘文件夹名并使用双输入弹窗
function renameModuleFolder(folderId, currentName) {
// 通过 data-folder-id 找到模块文件夹的行
// 修复:避免在 JS 字符串中使用内嵌模板字符串,改用拼接以解决编译问题
const editableSpan = document.querySelector('.module-folder-name[data-folder-id="' + folderId + '"]');
if (!editableSpan) return;
const folderRow = editableSpan.closest('tr');
// 从第三列的 data-disk-name 属性中获取当前的磁盘文件夹名称
const fileNameSpan = folderRow.cells[2].querySelector('.module-disk-path');
const currentFolderName = fileNameSpan.getAttribute('data-disk-name');
showModuleFolderRenameDialog(folderId, currentName, currentFolderName);
}
// 模块管理功能
// 原始的 editConfigName 已被修改为接受三个参数
// 在 VSCode 中打开配置文件
function openConfigFileInVSCode(configId) {
vscode.postMessage({
type: 'openConfigFileInVSCode',
configId: configId
});
}
// 统一功能:打开模块文件夹
function openTheModuleFolder(id, type) {
vscode.postMessage({
type: 'openTheModuleFolder',
id: id,
moduleType: type
});
}
function createNewConfig() {
showPromptDialog(
'新建配置',
'请输入配置名称:',
'',
function(name) {
if (name) {
vscode.postMessage({
type: 'createConfig',
name: name
});
}
}
);
}
function deleteConfig(configId) {
showConfirmDialog(
'确认删除',
'确定删除这个配置文件吗?',
function() {
vscode.postMessage({
type: 'deleteConfig',
configId: configId
});
},
function() {
}
);
}
// 删除模块文件夹功能
function deleteModuleFolder(folderId) {
showConfirmDialog(
'确认删除模块文件夹',
'确定删除这个模块文件夹吗?这将删除文件夹及其所有内容。',
function() {
vscode.postMessage({
type: 'deleteModuleFolder',
folderId: folderId
});
},
function() {
}
);
}
// 上传模块文件夹功能
function uploadModuleFolder(folderId, folderType) {
if (folderType === 'git') {
vscode.postMessage({
type: 'openRepoSelectForGitUpload',
folderId: folderId
});
} else {
vscode.postMessage({
type: 'openRepoSelectForUpload',
folderId: folderId
});
}
}
function goBackToContainers() {
vscode.postMessage({ type: 'goBackToContainers' });
}
// 通过弹窗获取仓库
function openRepoSelect() {
vscode.postMessage({
type: 'openRepoSelect'
});
}
function toggleBranch(branchName) {
const checkbox = document.getElementById('branch-' + branchName.replace(/[^a-zA-Z0-9-]/g, '-'));
if (checkbox.checked) {
selectedBranches.add(branchName);
} else {
selectedBranches.delete(branchName);
}
}
function cloneSelectedBranches() {
if (selectedBranches.size === 0) {
alert('请至少选择一个分支');
return;
}
vscode.postMessage({
type: 'cloneBranches',
branches: Array.from(selectedBranches)
});
selectedBranches.clear();
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(checkbox => checkbox.checked = false);
document.getElementById('branchSelectionContainer').innerHTML = '';
}
function cancelBranchSelection() {
selectedBranches.clear();
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(checkbox => checkbox.checked = false);
document.getElementById('branchSelectionContainer').innerHTML = '';
vscode.postMessage({
type: 'cancelBranchSelection'
});
}
// 配置文件合并功能
function toggleConfigSelection(configId) {
const checkbox = document.querySelector('.config-checkbox[data-id="' + configId + '"]');
if (checkbox.checked) {
selectedConfigs.add(configId);
} else {
selectedConfigs.delete(configId);
}
updateMergeButtonState();
}
function updateMergeButtonState() {
const mergeButton = document.getElementById('mergeButton');
if (mergeButton) {
mergeButton.disabled = selectedConfigs.size < 2;
}
}
function mergeSelectedConfigs() {
if (selectedConfigs.size < 2) {
alert('请至少选择两个配置文件进行合并');
return;
}
showMergeDialog();
}
// 合并对话框
function showMergeDialog() {
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
overlay.id = 'mergeModal';
overlay.innerHTML = \`
<div class="modal-dialog" style="width: 450px;">
<div class="modal-title">合并配置文件</div>
<div class="merge-dialog-content">
<div class="merge-input-section">
<label class="merge-input-label">配置显示名称</label>
<input type="text" id="displayNameInput" class="merge-input-field"
placeholder="在配置栏显示的名称" value="合并配置">
<div class="merge-input-help">在左侧配置栏显示的名称</div>
</div>
<div class="merge-input-section">
<label class="merge-input-label">文件夹名称</label>
<input type="text" id="folderNameInput" class="merge-input-field"
placeholder="文件夹的实际名称" value="merged-config">
<div class="merge-input-help">磁盘上的实际文件夹名称</div>
</div>
</div>
<div class="modal-buttons">
<button class="modal-btn modal-btn-secondary" onclick="closeMergeDialog()">取消</button>
<button class="modal-btn modal-btn-primary" onclick="confirmMerge()">确定合并</button>
</div>
</div>
\`;
document.body.appendChild(overlay);
setTimeout(() => {
const displayNameInput = document.getElementById('displayNameInput');
if (displayNameInput) {
displayNameInput.focus();
displayNameInput.select();
}
}, 100);
}
function confirmMerge() {
const displayNameInput = document.getElementById('displayNameInput');
const folderNameInput = document.getElementById('folderNameInput');
const displayName = displayNameInput.value.trim();
const folderName = folderNameInput.value.trim();
if (!displayName) {
alert('请输入配置显示名称');
return;
}
if (!folderName) {
alert('请输入文件夹名称');
return;
}
if (displayName && folderName) {
vscode.postMessage({
type: 'mergeConfigs',
configIds: Array.from(selectedConfigs),
displayName: displayName,
folderName: folderName
});
selectedConfigs.clear();
const checkboxes = document.querySelectorAll('.config-checkbox');
checkboxes.forEach(checkbox => checkbox.checked = false);
updateMergeButtonState();
closeMergeDialog();
}
}
function closeMergeDialog() {
const modal = document.getElementById('mergeModal');
if (modal) {
modal.remove();
}
}
// 树状分支功能
function toggleBranchNode(nodeId) {
const node = findNodeById(branchTreeData, nodeId);
if (node && !node.isLeaf) {
node.expanded = !node.expanded;
renderBranchTree(branchTreeData);
}
}
function expandAllBranches() {
setAllExpanded(branchTreeData, true);
renderBranchTree(branchTreeData);
}
function collapseAllBranches() {
setAllExpanded(branchTreeData, false);
renderBranchTree(branchTreeData);
}
function setAllExpanded(nodes, expanded) {
nodes.forEach(node => {
if (!node.isLeaf) {
node.expanded = expanded;
if (node.children) {
setAllExpanded(node.children, expanded);
}
}
});
}
function findNodeById(nodes, nodeId) {
for (const node of nodes) {
if (node.fullName === nodeId) return node;
if (node.children) {
const found = findNodeById(node.children, nodeId);
if (found) return found;
}
}
return null;
}
// 动态渲染树状分支
function renderBranchTree(treeData) {
const container = document.getElementById('branchSelectionContainer');
if (!treeData || treeData.length === 0) {
container.innerHTML = '<div style="text-align: center; padding: 10px; color: var(--vscode-descriptionForeground);">未找到分支</div>';
return;
}
let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">';
html += '<h4 class="branch-tree-title" style="margin: 0 0 8px 0;"><span class="tree-icon">🌳</span> 分支树</h4>';
html += '<div style="margin-bottom: 8px;">';
html += '<button class="back-btn" onclick="expandAllBranches()" style="margin-right: 8px; padding: 2px 6px; font-size: 11px;">展开全部</button>';
html += '<button class="back-btn" onclick="collapseAllBranches()" style="padding: 2px 6px; font-size: 11px;">收起全部</button>';
html += '</div>';
html += '<div class="branch-tree">';
html += renderBranchNodes(treeData, 0);
html += '</div>';
html += '<div style="margin-top: 12px;">';
html += '<button class="btn-new" onclick="cloneSelectedBranches()" style="margin-right: 8px; padding: 4px 8px; font-size: 12px;">✅ 克隆选中分支</button>';
html += '<button class="back-btn" onclick="cancelBranchSelection()" style="padding: 4px 8px; font-size: 12px;">取消</button>';
html += '</div></div>';
container.innerHTML = html;
}
function renderBranchNodes(nodes, level) {
let html = '';
nodes.forEach(node => {
const indent = level * 20;
const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-');
if (node.isLeaf) {
html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">';
html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" ';
html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\\'' + node.fullName + '\\')">';
html += '<span class="branch-icon">🌿</span>';
html += '<span class="branch-name ' + (node.branch && node.branch.isCurrent ? 'current-branch' : '') + '">';
html += node.name;
html += (node.branch && node.branch.isCurrent ? ' ⭐' : '');
html += '</span>';
html += '</div>';
} else {
html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">';
html += '<span class="branch-expand" onclick="toggleBranchNode(\\'' + node.fullName + '\\')">';
html += (node.expanded ? '🪵' : '🪵');
html += '</span>';
html += '<span class="branch-icon">🪵</span>';
html += '<span class="branch-name">' + node.name + '</span>';
html += '</div>';
if (node.expanded && node.children) {
html += '<div class="branch-children">';
html += renderBranchNodes(node.children, level + 1);
html += '</div>';
}
}
});
return html;
}
// 消息监听
window.addEventListener('message', event => {
const message = event.data;
if (message.type === 'branchesFetched') {
branchTreeData = message.branchTree || [];
renderBranchTree(branchTreeData);
}
});
// 初始化
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('change', function(event) {
if (event.target.classList.contains('config-checkbox')) {
const configId = event.target.getAttribute('data-id');
toggleConfigSelection(configId);
}
});
});
</script>
</body>
</html>`;
}
generateBranchesTreeHtml(branches) {
if (branches.length === 0)
return '';
const branchTree = this.buildBranchTree(branches);
let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">';
html += '<h4 class="branch-tree-title" style="margin: 0 0 8px 0;"><span class="tree-icon">🌳</span> 分支树</h4>';
html += '<div style="margin-bottom: 8px;">';
html += '<button class="back-btn" onclick="expandAllBranches()" style="margin-right: 8px; padding: 2px 6px; font-size: 11px;">展开全部</button>';
html += '<button class="back-btn" onclick="collapseAllBranches()" style="padding: 2px 6px; font-size: 11px;">收起全部</button>';
html += '</div>';
html += '<div class="branch-tree">';
html += this.renderBranchTreeNodes(branchTree, 0);
html += '</div>';
html += '<div style="margin-top: 12px;">';
html += '<button class="btn-new" onclick="cloneSelectedBranches()" style="margin-right: 8px; padding: 4px 8px; font-size: 12px;">✅ 克隆选中分支</button>';
html += '<button class="back-btn" onclick="cancelBranchSelection()" style="padding: 4px 8px; font-size: 12px;">取消</button>';
html += '</div></div>';
return html;
}
buildBranchTree(branches) {
const root = [];
branches.forEach(branch => {
const parts = branch.name.split('/');
let currentLevel = root;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
const isLeaf = i === parts.length - 1;
const fullName = parts.slice(0, i + 1).join('/');
let node = currentLevel.find(n => n.name === part);
if (!node) {
node = {
name: part,
fullName: fullName,
isLeaf: isLeaf,
children: [],
level: i,
expanded: true
};
currentLevel.push(node);
}
if (isLeaf) {
node.branch = branch;
}
currentLevel = node.children;
}
});
return root;
}
renderBranchTreeNodes(nodes, level) {
let html = '';
nodes.forEach(node => {
const indent = level * 20;
const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-');
if (node.isLeaf) {
html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">';
html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" ';
html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\'' + node.fullName + '\')">';
html += '<span class="branch-icon">🌿</span>';
html += '<span class="branch-name ' + (node.branch && node.branch.isCurrent ? 'current-branch' : '') + '">';
html += node.name;
html += (node.branch && node.branch.isCurrent ? ' ⭐' : '');
html += '</span>';
html += '</div>';
}
else {
html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">';
html += '<span class="branch-expand" onclick="toggleBranchNode(\'' + node.fullName + '\')">';
html += (node.expanded ? '🪵' : '🪵');
html += '</span>';
html += '<span class="branch-icon">🪵</span>';
html += '<span class="branch-name">' + node.name + '</span>';
html += '</div>';
if (node.expanded && node.children && node.children.length > 0) {
html += '<div class="branch-children">';
html += this.renderBranchTreeNodes(node.children, level + 1);
html += '</div>';
}
}
});
return html;
}
}
exports.ConfigView = ConfigView;
//# sourceMappingURL=ConfigView.js.map