0
0
Files
vs-p/out/panels/ConfigPanel.js
2025-12-02 14:48:30 +08:00

1752 lines
78 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigPanel = void 0;
// src/panels/ConfigPanel.ts
const vscode = __importStar(require("vscode"));
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
const isomorphic_git_1 = __importDefault(require("isomorphic-git"));
const node_1 = __importDefault(require("isomorphic-git/http/node"));
const ProjectView_1 = require("./views/ProjectView");
const AircraftView_1 = require("./views/AircraftView");
const ContainerView_1 = require("./views/ContainerView");
const ConfigView_1 = require("./views/ConfigView");
// =============================================
// 主面板类
// =============================================
class ConfigPanel {
// =============================================
// 公共方法
// =============================================
static createOrShow(extensionUri) {
const column = vscode.window.activeTextEditor?.viewColumn || vscode.ViewColumn.One;
if (ConfigPanel.currentPanel) {
ConfigPanel.currentPanel.panel.reveal(column);
return;
}
const panel = vscode.window.createWebviewPanel('DCSP', '数字卫星构建平台', column, {
enableScripts: true,
localResourceRoots: [extensionUri],
retainContextWhenHidden: true
});
ConfigPanel.currentPanel = new ConfigPanel(panel, extensionUri);
}
constructor(panel, extensionUri) {
// 视图状态管理
this.currentView = 'projects';
this.currentProjectId = '';
this.currentAircraftId = '';
this.currentContainerId = '';
this.currentModuleFolderId = '';
// 数据存储
this.projects = [];
this.aircrafts = [];
this.containers = [];
this.configs = [];
this.moduleFolders = [];
this.currentModuleFolderFileTree = [];
this.projectPaths = new Map();
this.pendingUploadFolderId = null;
// 仓库配置
this.repoConfigs = [];
// 状态管理
this.isWebviewDisposed = false;
this.panel = panel;
this.extensionUri = extensionUri;
this.isWebviewDisposed = false;
// 初始化视图
this.projectView = new ProjectView_1.ProjectView(extensionUri);
this.aircraftView = new AircraftView_1.AircraftView(extensionUri);
this.containerView = new ContainerView_1.ContainerView(extensionUri);
this.configView = new ConfigView_1.ConfigView(extensionUri);
// 尝试加载仓库配置
void this.loadRepoConfigs();
this.updateWebview();
this.setupMessageListener();
this.panel.onDidDispose(() => {
this.isWebviewDisposed = true;
ConfigPanel.currentPanel = undefined;
});
}
// =============================================
// 工具方法 - ID 生成
// =============================================
generateUniqueId(prefix, existingItems) {
let idNumber = 1;
// 先找到当前最大的数字
const existingIds = existingItems.map(item => item.id);
const numberPattern = /\d+$/;
for (const id of existingIds) {
const match = id.match(numberPattern);
if (match) {
const num = parseInt(match[0]);
if (num >= idNumber) {
idNumber = num + 1;
}
}
}
return `${prefix}${idNumber}`;
}
// =============================================
// 仓库配置相关
// =============================================
getRepoConfigPath() {
// 按你的需求:配置文件保存在插件安装位置
return path.join(this.extensionUri.fsPath, 'dcsp-repos.json');
}
async loadRepoConfigs() {
try {
const configPath = this.getRepoConfigPath();
if (!fs.existsSync(configPath)) {
this.repoConfigs = [];
return;
}
const content = await fs.promises.readFile(configPath, 'utf8');
if (!content.trim()) {
this.repoConfigs = [];
return;
}
const parsed = JSON.parse(content);
if (Array.isArray(parsed)) {
// 兼容老格式:直接是数组
this.repoConfigs = parsed;
}
else if (parsed && Array.isArray(parsed.repos)) {
this.repoConfigs = parsed.repos;
}
else {
this.repoConfigs = [];
}
}
catch (error) {
console.error('加载仓库配置失败:', error);
vscode.window.showErrorMessage('读取仓库配置文件失败dcsp-repos.json请检查文件格式。');
this.repoConfigs = [];
}
}
async ensureRepoConfigFileExists() {
const configPath = this.getRepoConfigPath();
if (!fs.existsSync(configPath)) {
const defaultContent = JSON.stringify({
repos: [
{
name: 'example-repo',
url: 'https://github.com/username/repo.git',
username: '',
password: ''
}
]
}, null, 2);
await fs.promises.writeFile(configPath, defaultContent, 'utf8');
}
}
async openRepoConfig() {
try {
await this.ensureRepoConfigFileExists();
const configPath = this.getRepoConfigPath();
const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(configPath));
await vscode.window.showTextDocument(doc);
await this.loadRepoConfigs();
}
catch (error) {
vscode.window.showErrorMessage(`打开仓库配置文件失败: ${error}`);
}
}
async openRepoSelect() {
await this.loadRepoConfigs();
if (!this.repoConfigs || this.repoConfigs.length === 0) {
vscode.window.showWarningMessage('尚未配置任何仓库,请先点击右上角 “仓库配置” 按钮编辑 dcsp-repos.json。');
}
if (this.isWebviewDisposed)
return;
// 仅传递仓库名给前端,用于下拉选择
this.panel.webview.postMessage({
type: 'showRepoSelect',
repos: this.repoConfigs.map(r => ({ name: r.name }))
});
}
async handleRepoSelectedForBranches(repoName) {
await this.loadRepoConfigs();
const repo = this.repoConfigs.find(r => r.name === repoName);
if (!repo) {
vscode.window.showErrorMessage(`在仓库配置中未找到名为 "${repoName}" 的仓库,请检查 dcsp-repos.json。`);
return;
}
// ---------------------------------------------------
// 1⃣ 新逻辑Local 上传pendingUploadFolderId 不为空)
// ---------------------------------------------------
if (this.pendingUploadFolderId) {
const localFolderId = this.pendingUploadFolderId;
this.pendingUploadFolderId = null; // 必须清空,避免影响其他操作
console.log("🚀 Local 上传流程repo =", repoName, "folderId =", localFolderId);
const folder = this.moduleFolders.find(f => f.id === localFolderId);
if (!folder) {
vscode.window.showErrorMessage("未找到要上传的本地模块文件夹");
return;
}
// 生成本地路径
const fullPath = this.getModuleFolderFullPath(folder);
if (!fullPath) {
vscode.window.showErrorMessage("无法确定本地模块文件夹路径");
return;
}
// 自动从本地文件夹名生成分支名
const branchName = path.basename(fullPath);
console.log("🌿 自动生成分支名:", branchName);
// 正式执行上传
await this.uploadLocalModuleFolder(localFolderId, repo.url, branchName);
return;
}
// ---------------------------------------------------
// 2⃣ 旧逻辑:获取分支
// ---------------------------------------------------
this.currentRepoForBranches = repo;
await this.fetchBranchesForRepo(repo);
}
// =============================================
// Webview 消息处理
// =============================================
setupMessageListener() {
this.panel.webview.onDidReceiveMessage(async (data) => {
console.log('📨 收到Webview消息:', data);
if (this.isWebviewDisposed) {
console.log('⚠️ Webview 已被销毁,忽略消息');
return;
}
try {
await this.handleWebviewMessage(data);
}
catch (error) {
console.error('处理 Webview 消息时出错:', error);
if (!this.isWebviewDisposed) {
vscode.window.showErrorMessage(`处理操作时出错: ${error}`);
}
}
});
}
async handleWebviewMessage(data) {
const messageHandlers = {
// 导航相关
'openExistingProject': () => this.openExistingProject(),
'configureProject': (data) => this.handleConfigureProject(data),
'openProject': (data) => this.handleOpenProject(data),
'openAircraftConfig': (data) => this.handleOpenAircraftConfig(data),
'openContainerConfig': (data) => this.handleOpenContainerConfig(data),
'goBackToProjects': () => this.handleGoBackToProjects(),
'goBackToAircrafts': () => this.handleGoBackToAircrafts(),
'goBackToContainers': () => this.handleGoBackToContainers(),
// 项目管理
'updateProjectName': (data) => this.updateProjectName(data.projectId, data.name),
'createProject': (data) => this.createProject(data.name),
'deleteProject': (data) => this.deleteProject(data.projectId),
// 飞行器管理
'updateAircraftName': (data) => this.updateAircraftName(data.aircraftId, data.name),
'createAircraft': (data) => this.createAircraft(data.name),
'deleteAircraft': (data) => this.deleteAircraft(data.aircraftId),
// 容器管理
'updateContainerName': (data) => this.updateContainerName(data.containerId, data.name),
'createContainer': (data) => this.createContainer(data.name),
'deleteContainer': (data) => this.deleteContainer(data.containerId),
// 配置管理
'updateConfigName': (data) => this.updateConfigName(data.configId, data.name),
'createConfig': (data) => this.createConfig(data.name),
'deleteConfig': (data) => this.deleteConfig(data.configId),
'openConfigFileInVSCode': (data) => this.openConfigFileInVSCode(data.configId),
'mergeConfigs': (data) => this.mergeConfigs(data.configIds, data.displayName, data.folderName),
// Git 仓库管理(新:基于配置)
'openRepoConfig': () => this.openRepoConfig(),
'openRepoSelect': () => this.openRepoSelect(),
'repoSelectedForBranches': (data) => this.handleRepoSelectedForBranches(data.repoName),
// Git 仓库管理(老接口:为了兼容,仍然保留)
'fetchBranches': (data) => this.fetchBranches(data.url),
'cloneBranches': (data) => this.cloneBranches(data.branches),
'cancelBranchSelection': () => this.handleCancelBranchSelection(),
// 模块文件夹管理
'loadModuleFolder': (data) => this.loadModuleFolder(data.folderId),
'syncGitModuleFolder': (data) => this.syncGitModuleFolder(data.folderId),
'deleteModuleFolder': (data) => this.deleteModuleFolder(data.folderId),
'importGitFile': (data) => this.importGitFile(data.filePath),
'openTheModuleFolder': (data) => this.openTheModuleFolder(data.moduleType, data.id),
'renameModuleFolder': (data) => this.renameModuleFolder(data.folderId, data.newName),
// 上传功能
'uploadGitModuleFolder': (data) => this.uploadGitModuleFolder(data.folderId, data.username, data.password),
'openRepoSelectForUpload': (data) => this.handleOpenRepoSelectForUpload(data.folderId),
'uploadLocalModuleFolder': (data) => this.uploadLocalModuleFolder(data.folderId, data.repoUrl, data.branchName)
};
const handler = messageHandlers[data.type];
if (handler) {
await handler(data);
}
else {
console.warn(`未知的消息类型: ${data.type}`);
}
}
// =============================================
// 导航处理方法
// =============================================
async handleConfigureProject(data) {
const selectedPath = await this.selectProjectPath(data.projectId, data.projectName);
if (selectedPath) {
this.currentView = 'aircrafts';
this.currentProjectId = data.projectId;
this.updateWebview();
}
}
async handleOpenProject(data) {
this.currentView = 'aircrafts';
this.currentProjectId = data.projectId;
this.updateWebview();
}
async handleOpenAircraftConfig(data) {
this.currentView = 'containers';
this.currentProjectId = data.projectId;
this.currentAircraftId = data.aircraftId;
this.updateWebview();
}
async handleOpenContainerConfig(data) {
this.currentView = 'configs';
this.currentContainerId = data.containerId;
this.updateWebview();
}
async handleGoBackToProjects() {
this.currentView = 'projects';
this.currentProjectId = '';
this.currentAircraftId = '';
this.currentContainerId = '';
this.currentModuleFolderId = '';
this.updateWebview();
}
async handleGoBackToAircrafts() {
this.currentView = 'aircrafts';
this.currentAircraftId = '';
this.currentContainerId = '';
this.updateWebview();
}
async handleGoBackToContainers() {
this.currentView = 'containers';
this.currentContainerId = '';
this.updateWebview();
}
async handleCancelBranchSelection() {
console.log('❌ 取消分支选择');
this.updateWebview();
}
// =============================================
// 项目管理方法
// =============================================
async updateProjectName(projectId, newName) {
const project = this.projects.find(p => p.id === projectId);
if (project) {
project.name = newName;
vscode.window.showInformationMessage(`项目名称更新: ${newName}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
}
async createProject(name) {
const newId = this.generateUniqueId('p', this.projects);
const newProject = {
id: newId,
name: name
};
this.projects.push(newProject);
this.currentProjectId = newId;
vscode.window.showInformationMessage(`新建项目: ${name}`);
this.updateWebview();
}
async deleteProject(projectId) {
const project = this.projects.find(p => p.id === projectId);
if (!project)
return;
this.projects = this.projects.filter(p => p.id !== projectId);
const relatedAircrafts = this.aircrafts.filter(a => a.projectId === projectId);
const aircraftIds = relatedAircrafts.map(a => a.id);
this.aircrafts = this.aircrafts.filter(a => a.projectId !== projectId);
this.containers = this.containers.filter(c => !aircraftIds.includes(c.aircraftId));
const containerIds = this.containers.filter(c => aircraftIds.includes(c.aircraftId)).map(c => c.id);
this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId));
this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId));
this.projectPaths.delete(projectId);
vscode.window.showInformationMessage(`删除项目: ${project.name}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
// =============================================
// 飞行器管理方法
// =============================================
async updateAircraftName(aircraftId, newName) {
const aircraft = this.aircrafts.find(a => a.id === aircraftId);
if (aircraft) {
aircraft.name = newName;
vscode.window.showInformationMessage(`飞行器名称更新: ${newName}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
}
async createAircraft(name) {
if (!this.currentProjectId) {
vscode.window.showErrorMessage('无法创建飞行器:未找到当前项目');
return;
}
const newId = this.generateUniqueId('a', this.aircrafts);
const newAircraft = {
id: newId,
name: name,
projectId: this.currentProjectId
};
this.aircrafts.push(newAircraft);
await this.createAircraftDirectory(newAircraft);
vscode.window.showInformationMessage(`新建飞行器: ${name}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
async deleteAircraft(aircraftId) {
const aircraft = this.aircrafts.find(a => a.id === aircraftId);
if (!aircraft)
return;
this.aircrafts = this.aircrafts.filter(a => a.id !== aircraftId);
this.containers = this.containers.filter(c => c.aircraftId !== aircraftId);
const containerIds = this.containers.filter(c => c.aircraftId === aircraftId).map(c => c.id);
this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId));
this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId));
vscode.window.showInformationMessage(`删除飞行器: ${aircraft.name}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
// =============================================
// 容器管理方法
// =============================================
async updateContainerName(containerId, newName) {
const container = this.containers.find(c => c.id === containerId);
if (container) {
container.name = newName;
vscode.window.showInformationMessage(`容器名称更新: ${newName}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
}
async createContainer(name) {
if (!this.currentAircraftId) {
vscode.window.showErrorMessage('无法创建容器:未找到当前飞行器');
return;
}
const newId = this.generateUniqueId('c', this.containers);
const newContainer = {
id: newId,
name: name,
aircraftId: this.currentAircraftId
};
this.containers.push(newContainer);
await this.createContainerDirectory(newContainer);
await this.createDefaultConfigs(newContainer);
vscode.window.showInformationMessage(`新建容器: ${name} (包含2个默认配置文件)`);
await this.saveCurrentProjectData();
this.updateWebview();
}
async deleteContainer(containerId) {
const container = this.containers.find(c => c.id === containerId);
if (!container)
return;
this.containers = this.containers.filter(c => c.id !== containerId);
this.configs = this.configs.filter(cfg => cfg.containerId !== containerId);
this.moduleFolders = this.moduleFolders.filter(folder => folder.containerId !== containerId);
vscode.window.showInformationMessage(`删除容器: ${container.name}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
// =============================================
// 配置管理方法
// =============================================
async updateConfigName(configId, newName) {
const config = this.configs.find(c => c.id === configId);
if (config) {
config.name = newName;
vscode.window.showInformationMessage(`配置名称更新: ${newName}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
}
async createConfig(name) {
const newId = this.generateUniqueId('cfg', this.configs);
const newConfig = {
id: newId,
name: name,
fileName: name.toLowerCase().replace(/\s+/g, '_'),
content: `# ${name} 配置文件\n# 创建时间: ${new Date().toLocaleString()}\n# 您可以在此编辑配置内容\n\n`,
containerId: this.currentContainerId
};
this.configs.push(newConfig);
await this.ensureContainerDirectoryExists(this.currentContainerId);
vscode.window.showInformationMessage(`新建配置: ${name}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
async handleOpenRepoSelectForUpload(folderId) {
console.log("📌 Local 上传:收到 openRepoSelectForUploadfolderId =", folderId);
this.pendingUploadFolderId = folderId;
// 复用你现有的仓库选择弹窗
await this.openRepoSelect();
}
async deleteConfig(configId) {
const config = this.configs.find(c => c.id === configId);
if (!config)
return;
const confirm = await vscode.window.showWarningMessage(`确定要删除配置文件 "${config.name}" 吗?这将同时删除磁盘上的文件。`, { modal: true }, '确定删除', '取消');
if (confirm !== '确定删除') {
return;
}
try {
this.configs = this.configs.filter(c => c.id !== configId);
await this.deleteConfigFileFromDisk(config);
vscode.window.showInformationMessage(`删除配置: ${config.name}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
catch (error) {
vscode.window.showErrorMessage(`删除配置文件失败: ${error}`);
}
}
async mergeConfigs(configIds, displayName, folderName) {
if (!this.currentContainerId) {
vscode.window.showErrorMessage('未找到当前容器');
return;
}
if (configIds.length < 2) {
vscode.window.showErrorMessage('请至少选择两个配置文件进行合并');
return;
}
try {
const container = this.containers.find(c => c.id === this.currentContainerId);
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!container || !aircraft || !projectPath) {
vscode.window.showErrorMessage('未找到相关项目数据');
return;
}
const selectedConfigs = this.configs.filter(config => configIds.includes(config.id));
if (selectedConfigs.length !== configIds.length) {
vscode.window.showErrorMessage('部分配置文件未找到');
return;
}
const mergeFolderPath = path.join(projectPath, aircraft.name, container.name, folderName);
await fs.promises.mkdir(mergeFolderPath, { recursive: true });
for (const config of selectedConfigs) {
const sourcePath = path.join(projectPath, aircraft.name, container.name, config.fileName);
const targetPath = path.join(mergeFolderPath, config.fileName);
if (fs.existsSync(sourcePath)) {
await fs.promises.copyFile(sourcePath, targetPath);
}
else {
await fs.promises.writeFile(targetPath, config.content || '');
}
}
const relativePath = `/${aircraft.projectId}/${aircraft.name}/${container.name}/${folderName}`;
const newId = this.generateUniqueId('local-', this.moduleFolders);
const newFolder = {
id: newId,
name: displayName,
type: 'local',
localPath: relativePath,
containerId: this.currentContainerId
};
this.moduleFolders.push(newFolder);
for (const configId of configIds) {
await this.deleteConfigInternal(configId);
}
await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`成功合并 ${selectedConfigs.length} 个配置文件到文件夹: ${folderName}`);
this.updateWebview();
}
catch (error) {
console.error('❌ 合并配置文件失败:', error);
vscode.window.showErrorMessage(`合并配置文件失败: ${error}`);
}
}
// =============================================
// Git 分支管理方法
// =============================================
async fetchBranchesForRepo(repo) {
await this.fetchBranches(repo.url, repo);
}
async fetchBranches(url, repo) {
try {
console.log('🌿 开始获取分支列表:', url);
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: '正在获取分支信息',
cancellable: false
}, async (progress) => {
progress.report({ increment: 0, message: '连接远程仓库...' });
try {
progress.report({ increment: 30, message: '获取远程引用...' });
const options = {
http: node_1.default,
url: url
};
if (repo && (repo.username || repo.password)) {
options.onAuth = () => ({
username: repo.username || '',
password: repo.password || ''
});
}
const refs = await isomorphic_git_1.default.listServerRefs(options);
console.log('📋 获取到的引用:', refs);
const branchRefs = refs.filter((ref) => ref.ref.startsWith('refs/heads/') || ref.ref.startsWith('refs/remotes/origin/'));
const branches = branchRefs.map((ref) => {
let branchName;
if (ref.ref.startsWith('refs/remotes/')) {
branchName = ref.ref.replace('refs/remotes/origin/', '');
}
else {
branchName = ref.ref.replace('refs/heads/', '');
}
return {
name: branchName,
isCurrent: branchName === 'main' || branchName === 'master',
selected: false
};
});
if (branches.length === 0) {
throw new Error('未找到任何分支');
}
progress.report({ increment: 80, message: '处理分支数据...' });
const branchTree = this.buildBranchTree(branches);
if (!this.isWebviewDisposed) {
this.panel.webview.postMessage({
type: 'branchesFetched',
branches: branches,
branchTree: branchTree,
repoUrl: url,
repoName: repo?.name
});
}
progress.report({ increment: 100, message: '完成' });
}
catch (error) {
console.error('❌ 使用 listServerRefs 获取分支失败:', error);
vscode.window.showErrorMessage(`获取分支失败: ${error}`);
}
});
}
catch (error) {
console.error('❌ 获取分支失败:', error);
vscode.window.showErrorMessage(`获取分支失败: ${error}`);
}
}
async cloneBranches(branches) {
if (!this.currentRepoForBranches) {
vscode.window.showErrorMessage('请先通过“获取仓库”选择一个仓库');
return;
}
const url = this.currentRepoForBranches.url;
const repoDisplayName = this.currentRepoForBranches.name;
try {
console.log('🚀 开始克隆分支:', { url, branches });
let successCount = 0;
let failCount = 0;
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `正在克隆 ${branches.length} 个分支`,
cancellable: false
}, async (progress) => {
for (let i = 0; i < branches.length; i++) {
const branch = branches[i];
const progressPercent = (i / branches.length) * 100;
progress.report({
increment: progressPercent,
message: `克隆分支: ${branch} (${i + 1}/${branches.length})`
});
console.log(`📥 开始克隆分支: ${branch}`);
try {
const folderNames = this.generateModuleFolderName(url, branch);
await this.addGitModuleFolder(url, repoDisplayName, folderNames.folderName, branch);
successCount++;
console.log(`✅ 分支克隆成功: ${branch}`);
}
catch (error) {
failCount++;
console.error(`❌ 分支克隆失败: ${branch}`, error);
}
}
});
if (failCount === 0) {
vscode.window.showInformationMessage(`成功克隆 ${successCount} 个分支`);
}
else {
vscode.window.showWarningMessage(`克隆完成: ${successCount} 个成功, ${failCount} 个失败`);
}
}
catch (error) {
console.error('❌ 克隆分支失败:', error);
vscode.window.showErrorMessage(`克隆分支失败: ${error}`);
}
}
// =============================================
// 模块文件夹管理方法
// =============================================
async addGitModuleFolder(url, displayName, folderName, branch) {
try {
if (!url || !url.startsWith('http')) {
vscode.window.showErrorMessage('请输入有效的 Git 仓库 URL');
return;
}
if (!this.currentContainerId) {
vscode.window.showErrorMessage('请先选择容器');
return;
}
const container = this.containers.find(c => c.id === this.currentContainerId);
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!container || !aircraft || !projectPath) {
vscode.window.showErrorMessage('未找到相关项目数据');
return;
}
const folderId = this.generateUniqueId('git-', this.moduleFolders);
const relativePath = `/${aircraft.projectId}/${aircraft.name}/${container.name}/${folderName}`;
const localPath = path.join(projectPath, aircraft.name, container.name, folderName);
console.log(`📁 准备克隆仓库: ${displayName}, 分支: ${branch}, 路径: ${localPath}`);
const existingFolder = this.moduleFolders.find(folder => folder.localPath === relativePath && folder.containerId === this.currentContainerId);
if (existingFolder) {
vscode.window.showWarningMessage(`该路径的模块文件夹已存在: ${folderName}`);
return;
}
const newFolder = {
id: folderId,
name: displayName,
type: 'git',
localPath: relativePath,
containerId: this.currentContainerId
};
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `正在克隆仓库: ${displayName}`,
cancellable: false
}, async (progress) => {
progress.report({ increment: 0 });
try {
const parentDir = path.dirname(localPath);
await fs.promises.mkdir(parentDir, { recursive: true });
let dirExists = false;
try {
await fs.promises.access(localPath);
dirExists = true;
}
catch {
dirExists = false;
}
if (dirExists) {
const dirContents = await fs.promises.readdir(localPath);
if (dirContents.length > 0) {
const confirm = await vscode.window.showWarningMessage(`目标目录 "${folderName}" 不为空,确定要覆盖吗?`, { modal: true }, '确定覆盖', '取消');
if (confirm !== '确定覆盖') {
vscode.window.showInformationMessage('克隆操作已取消');
return;
}
for (const item of dirContents) {
const itemPath = path.join(localPath, item);
if (item !== '.git') {
await fs.promises.rm(itemPath, { recursive: true, force: true });
}
}
}
}
console.log(`🚀 开始克隆: ${url} -> ${localPath}, 分支: ${branch}`);
await isomorphic_git_1.default.clone({
fs: fs,
http: node_1.default,
dir: localPath,
url: url,
singleBranch: true,
depth: 1,
ref: branch || 'main',
onProgress: (event) => {
console.log(`📊 克隆进度: ${event.phase} - ${event.loaded}/${event.total}`);
if (event.total) {
const percent = (event.loaded / event.total) * 100;
progress.report({
increment: percent,
message: `${event.phase}... (${Math.round(percent)}%)`
});
}
else {
progress.report({ message: `${event.phase}...` });
}
}
});
console.log('✅ Git克隆成功完成');
const clonedContents = await fs.promises.readdir(localPath);
console.log(`📁 克隆后的目录内容:`, clonedContents);
if (clonedContents.length === 0) {
throw new Error('克隆后目录为空,可能克隆失败');
}
this.moduleFolders.push(newFolder);
await this.saveCurrentProjectData();
console.log('✅ Git模块文件夹数据已保存到项目文件');
vscode.window.showInformationMessage(`Git 仓库克隆成功: ${displayName}`);
if (!this.isWebviewDisposed) {
console.log('🌳 开始加载模块文件夹文件树...');
this.currentModuleFolderId = folderId;
await this.loadModuleFolderFileTree(folderId);
console.log('✅ 模块文件夹文件树加载完成');
}
}
catch (error) {
console.error('❌ 在克隆过程中捕获错误:', error);
try {
if (fs.existsSync(localPath)) {
await fs.promises.rm(localPath, { recursive: true, force: true });
console.log('🧹 已清理克隆失败的目录');
}
}
catch (cleanupError) {
console.error('清理失败目录时出错:', cleanupError);
}
vscode.window.showErrorMessage(`克隆仓库失败: ${error}`);
throw error;
}
});
}
catch (error) {
console.error('❌ 在addGitModuleFolder外部捕获错误:', error);
vscode.window.showErrorMessage(`添加 Git 模块文件夹失败: ${error}`);
}
}
async loadModuleFolder(folderId) {
this.currentModuleFolderId = folderId;
const folder = this.moduleFolders.find(f => f.id === folderId);
if (folder && folder.type === 'git') {
await this.loadModuleFolderFileTree(folderId);
}
this.updateWebview();
}
async syncGitModuleFolder(folderId) {
const folder = this.moduleFolders.find(f => f.id === folderId);
if (!folder || folder.type !== 'git') {
vscode.window.showErrorMessage('未找到指定的 Git 模块文件夹');
return;
}
const fullPath = this.getModuleFolderFullPath(folder);
if (!fullPath) {
vscode.window.showErrorMessage('无法获取模块文件夹的完整路径');
return;
}
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `正在同步仓库: ${folder.name}`,
cancellable: false
}, async (progress) => {
try {
progress.report({ increment: 0, message: '拉取最新更改...' });
await isomorphic_git_1.default.pull({
fs: fs,
http: node_1.default,
dir: fullPath,
author: { name: 'DCSP User', email: 'user@dcsp.local' },
fastForward: true
});
await this.loadModuleFolderFileTree(folderId);
vscode.window.showInformationMessage(`Git 仓库同步成功: ${folder.name}`);
this.updateWebview();
}
catch (error) {
vscode.window.showErrorMessage(`同步 Git 仓库失败: ${error}`);
}
});
}
async deleteModuleFolder(folderId) {
const folder = this.moduleFolders.find(f => f.id === folderId);
if (!folder)
return;
const confirm = await vscode.window.showWarningMessage(`确定要删除模块文件夹 "${folder.name}" 吗?这将删除本地文件。`, { modal: true }, '确定删除', '取消');
if (confirm === '确定删除') {
try {
const fullPath = this.getModuleFolderFullPath(folder);
if (fullPath) {
await fs.promises.rm(fullPath, { recursive: true, force: true });
}
this.moduleFolders = this.moduleFolders.filter(f => f.id !== folderId);
await this.saveCurrentProjectData();
if (this.currentModuleFolderId === folderId) {
this.currentModuleFolderId = '';
this.currentModuleFolderFileTree = [];
}
vscode.window.showInformationMessage(`模块文件夹已删除: ${folder.name}`);
this.updateWebview();
}
catch (error) {
vscode.window.showErrorMessage(`删除模块文件夹失败: ${error}`);
}
}
}
async importGitFile(filePath) {
if (!this.currentModuleFolderId || !this.currentContainerId) {
vscode.window.showErrorMessage('请先选择模块文件夹和容器');
return;
}
const folder = this.moduleFolders.find(f => f.id === this.currentModuleFolderId);
if (!folder || folder.type !== 'git') {
vscode.window.showErrorMessage('未找到当前 Git 模块文件夹');
return;
}
const container = this.containers.find(c => c.id === this.currentContainerId);
if (!container) {
vscode.window.showErrorMessage('未找到当前容器');
return;
}
try {
const fullPath = this.getModuleFolderFullPath(folder);
if (!fullPath) {
vscode.window.showErrorMessage('无法获取模块文件夹路径');
return;
}
const fileFullPath = path.join(fullPath, filePath);
const content = await fs.promises.readFile(fileFullPath, 'utf8');
const fileName = path.basename(filePath);
const newId = this.generateUniqueId('cfg', this.configs);
const newConfig = {
id: newId,
name: fileName,
fileName: fileName,
content: content,
containerId: this.currentContainerId
};
this.configs.push(newConfig);
await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`文件已导入到容器 ${container.name}: ${fileName}`);
this.updateWebview();
}
catch (error) {
vscode.window.showErrorMessage(`导入文件失败: ${error}`);
}
}
// =============================================
// 上传功能方法
// =============================================
async uploadGitModuleFolder(folderId, username, password) {
const folder = this.moduleFolders.find(f => f.id === folderId);
if (!folder || folder.type !== 'git') {
vscode.window.showErrorMessage('未找到指定的 Git 模块文件夹');
return;
}
const fullPath = this.getModuleFolderFullPath(folder);
if (!fullPath) {
vscode.window.showErrorMessage('无法获取模块文件夹的完整路径');
return;
}
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `正在上传 Git 仓库: ${folder.name}`,
cancellable: false
}, async (progress) => {
try {
progress.report({ increment: 0, message: '检查更改...' });
await this.commitAndPushUsingCommandLine(fullPath);
progress.report({ increment: 100, message: '完成' });
folder.uploaded = true;
await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`✅ Git 仓库上传成功: ${folder.name}`);
this.updateWebview();
}
catch (error) {
console.error('❌ Git 上传失败:', error);
vscode.window.showErrorMessage(`推送失败: ${error.message || error}`);
}
});
}
async uploadLocalModuleFolder(folderId, repoUrl, branchName) {
const folder = this.moduleFolders.find(f => f.id === folderId);
if (!folder || folder.type !== 'local') {
vscode.window.showErrorMessage('未找到指定的本地模块文件夹');
return;
}
const fullPath = this.getModuleFolderFullPath(folder);
if (!fullPath) {
vscode.window.showErrorMessage('无法获取模块文件夹的完整路径');
return;
}
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `正在上传本地文件夹到 Git 仓库: ${folder.name}`,
cancellable: false
}, async (progress) => {
try {
progress.report({ increment: 0, message: '检查目录...' });
if (!fs.existsSync(fullPath)) {
throw new Error('本地文件夹不存在');
}
progress.report({ increment: 10, message: '初始化 Git 仓库...' });
await this.initGitRepository(fullPath, branchName);
progress.report({ increment: 20, message: '添加远程仓库...' });
await this.addGitRemote(fullPath, repoUrl);
progress.report({ increment: 40, message: '提交初始文件...' });
await this.commitInitialFiles(fullPath);
progress.report({ increment: 60, message: '推送到远程仓库...' });
await this.pushToRemoteWithForce(fullPath, branchName);
progress.report({ increment: 100, message: '完成' });
folder.type = 'git';
folder.uploaded = true;
await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`本地文件夹成功上传到 Git 仓库: ${folder.name} -> ${branchName}`);
this.updateWebview();
}
catch (error) {
console.error('❌ 本地文件夹上传失败:', error);
vscode.window.showErrorMessage(`推送失败: ${error.message || error}`);
try {
const gitDir = path.join(fullPath, '.git');
if (fs.existsSync(gitDir)) {
await fs.promises.rm(gitDir, { recursive: true, force: true });
}
}
catch (cleanupError) {
console.error('清理 .git 文件夹失败:', cleanupError);
}
}
});
}
// =============================================
// Git 命令行工具方法
// =============================================
async commitAndPushUsingCommandLine(fullPath) {
return new Promise((resolve, reject) => {
const { exec } = require('child_process');
console.log('🚀 使用命令行 Git 提交并推送...');
console.log(`📁 工作目录: ${fullPath}`);
exec('git status --porcelain', {
cwd: fullPath,
encoding: 'utf8'
}, (statusError, statusStdout, statusStderr) => {
if (statusError) {
console.error('❌ 检查 Git 状态失败:', statusError);
reject(new Error(`检查 Git 状态失败: ${statusStderr || statusError.message}`));
return;
}
if (!statusStdout.trim()) {
console.log(' 没有需要提交的更改');
reject(new Error('没有需要提交的更改'));
return;
}
console.log('📋 检测到更改:', statusStdout);
const commands = [
'git add .',
`git commit -m "Auto commit from DCSP - ${new Date().toLocaleString()}"`,
'git push'
];
exec(commands.join(' && '), {
cwd: fullPath,
encoding: 'utf8'
}, (error, stdout, stderr) => {
console.log('📋 Git 命令输出:', stdout);
console.log('📋 Git 命令错误:', stderr);
if (error) {
console.error('❌ Git 提交/推送失败:', error);
reject(new Error(stderr || error.message));
return;
}
console.log('✅ Git 提交并推送成功');
resolve();
});
});
});
}
async initGitRepository(fullPath, branchName) {
return new Promise((resolve, reject) => {
const { exec } = require('child_process');
console.log('📁 初始化 Git 仓库...');
exec(`git init && git checkout -b ${branchName}`, {
cwd: fullPath,
encoding: 'utf8'
}, (error, stdout, stderr) => {
if (error) {
console.error('❌ Git 初始化失败:', error);
reject(new Error(stderr || error.message));
return;
}
console.log('✅ Git 初始化成功');
resolve();
});
});
}
async addGitRemote(fullPath, repoUrl) {
return new Promise((resolve, reject) => {
const { exec } = require('child_process');
console.log('📡 添加远程仓库...');
exec(`git remote add origin ${repoUrl}`, {
cwd: fullPath,
encoding: 'utf8'
}, (error, stdout, stderr) => {
if (error) {
console.error('❌ 添加远程仓库失败:', error);
reject(new Error(stderr || error.message));
return;
}
console.log('✅ 远程仓库添加成功');
resolve();
});
});
}
async commitInitialFiles(fullPath) {
return new Promise((resolve, reject) => {
const { exec } = require('child_process');
console.log('💾 提交初始文件...');
const commands = [
'git add .',
`git commit -m "Initial commit from DCSP - ${new Date().toLocaleString()}"`,
];
exec(commands.join(' && '), {
cwd: fullPath,
encoding: 'utf8'
}, (error, stdout, stderr) => {
if (error) {
if (stderr.includes('nothing to commit') || stdout.includes('nothing to commit')) {
console.log(' 没有需要提交的更改');
resolve();
return;
}
console.error('❌ Git 提交失败:', error);
reject(new Error(stderr || error.message));
return;
}
console.log('✅ 初始文件提交成功');
resolve();
});
});
}
async pushToRemoteWithForce(fullPath, branchName) {
return new Promise((resolve, reject) => {
const { exec } = require('child_process');
console.log('🚀 强制推送到远程仓库...');
exec(`git push -u origin ${branchName} --force`, {
cwd: fullPath,
encoding: 'utf8'
}, (error, stdout, stderr) => {
console.log('📋 Git push stdout:', stdout);
console.log('📋 Git push stderr:', stderr);
if (error) {
console.error('❌ Git 推送失败:', error);
reject(new Error(stderr || error.message));
return;
}
console.log('✅ Git 推送成功');
resolve();
});
});
}
// =============================================
// 文件系统操作方法
// =============================================
async createAircraftDirectory(aircraft) {
try {
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath) {
console.warn('未找到项目路径,跳过创建飞行器目录');
return;
}
const aircraftDir = vscode.Uri.joinPath(vscode.Uri.file(projectPath), aircraft.name);
await vscode.workspace.fs.createDirectory(aircraftDir);
console.log(`✅ 创建飞行器目录: ${aircraftDir.fsPath}`);
}
catch (error) {
console.error(`创建飞行器目录失败: ${error}`);
}
}
async createContainerDirectory(container) {
try {
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
if (!aircraft) {
console.warn('未找到对应的飞行器,跳过创建容器目录');
return;
}
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath) {
console.warn('未找到项目路径,跳过创建容器目录');
return;
}
const aircraftDir = vscode.Uri.joinPath(vscode.Uri.file(projectPath), aircraft.name);
const containerDir = vscode.Uri.joinPath(aircraftDir, container.name);
await vscode.workspace.fs.createDirectory(aircraftDir);
await vscode.workspace.fs.createDirectory(containerDir);
console.log(`✅ 创建容器目录: ${containerDir.fsPath}`);
}
catch (error) {
console.error(`创建容器目录失败: ${error}`);
}
}
async ensureContainerDirectoryExists(containerId) {
try {
const container = this.containers.find(c => c.id === containerId);
if (!container)
return;
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
if (!aircraft)
return;
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath)
return;
const aircraftDir = vscode.Uri.joinPath(vscode.Uri.file(projectPath), aircraft.name);
const containerDir = vscode.Uri.joinPath(aircraftDir, container.name);
await vscode.workspace.fs.createDirectory(aircraftDir);
await vscode.workspace.fs.createDirectory(containerDir);
}
catch (error) {
console.error(`确保容器目录存在失败: ${error}`);
}
}
async createDefaultConfigs(container) {
this.configs.push({
id: this.generateUniqueId('cfg', this.configs),
name: '配置1',
fileName: 'dockerfile',
content: `# ${container.name} 的 Dockerfile\nFROM ubuntu:20.04\n\n# 设置工作目录\nWORKDIR /app\n\n# 复制文件\nCOPY . .\n\n# 安装依赖\nRUN apt-get update && apt-get install -y \\\n python3 \\\n python3-pip\n\n# 暴露端口\nEXPOSE 8080\n\n# 启动命令\nCMD ["python3", "app.py"]`,
containerId: container.id
});
this.configs.push({
id: this.generateUniqueId('cfg', this.configs),
name: '配置2',
fileName: 'docker-compose.yml',
content: `# ${container.name} 的 Docker Compose 配置\nversion: '3.8'\n\nservices:\n ${container.name.toLowerCase().replace(/\s+/g, '-')}:\n build: .\n container_name: ${container.name}\n ports:\n - "8080:8080"\n environment:\n - NODE_ENV=production\n volumes:\n - ./data:/app/data\n restart: unless-stopped`,
containerId: container.id
});
}
async deleteConfigFileFromDisk(config) {
const container = this.containers.find(c => c.id === config.containerId);
if (!container)
return;
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
if (!aircraft)
return;
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath)
return;
const filePath = path.join(projectPath, aircraft.name, container.name, config.fileName);
if (fs.existsSync(filePath)) {
await fs.promises.unlink(filePath);
console.log(`✅ 已删除配置文件: ${filePath}`);
}
}
async deleteConfigInternal(configId) {
try {
const config = this.configs.find(c => c.id === configId);
if (!config)
return;
this.configs = this.configs.filter(c => c.id !== configId);
await this.deleteConfigFileFromDisk(config);
console.log(`✅ 内部删除配置: ${config.name}`);
}
catch (error) {
console.error(`删除配置文件失败: ${error}`);
}
}
// =============================================
// 项目数据持久化方法
// =============================================
async saveCurrentProjectData() {
try {
if (!this.currentProjectId) {
console.warn('未找到当前项目,数据将不会保存');
return;
}
const projectPath = this.projectPaths.get(this.currentProjectId);
if (!projectPath) {
console.warn('未找到项目存储路径,数据将不会保存');
return;
}
const dataUri = vscode.Uri.joinPath(vscode.Uri.file(projectPath), '.dcsp-data.json');
const data = {
projects: [this.projects.find(p => p.id === this.currentProjectId)],
aircrafts: this.aircrafts.filter(a => a.projectId === this.currentProjectId),
containers: this.containers.filter(c => {
const aircraft = this.aircrafts.find(a => a.id === c.aircraftId);
return aircraft && aircraft.projectId === this.currentProjectId;
}),
configs: this.configs.filter(cfg => {
const container = this.containers.find(c => c.id === cfg.containerId);
if (!container)
return false;
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
return aircraft && aircraft.projectId === this.currentProjectId;
}),
moduleFolders: this.moduleFolders.filter(folder => {
const container = this.containers.find(c => c.id === folder.containerId);
if (!container)
return false;
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
return aircraft && aircraft.projectId === this.currentProjectId;
})
};
const uint8Array = new TextEncoder().encode(JSON.stringify(data, null, 2));
await vscode.workspace.fs.writeFile(dataUri, uint8Array);
console.log('✅ 当前项目数据已保存,包含', data.moduleFolders.length, '个模块文件夹');
}
catch (error) {
vscode.window.showErrorMessage(`保存项目数据失败: ${error}`);
}
}
async loadProjectData(projectPath) {
try {
const dataUri = vscode.Uri.joinPath(vscode.Uri.file(projectPath), '.dcsp-data.json');
try {
await vscode.workspace.fs.stat(dataUri);
}
catch {
vscode.window.showErrorMessage('选择的文件夹中没有找到项目数据文件 (.dcsp-data.json)');
return false;
}
const fileData = await vscode.workspace.fs.readFile(dataUri);
const dataStr = new TextDecoder().decode(fileData);
const data = JSON.parse(dataStr);
const projectId = data.projects[0]?.id;
if (projectId) {
this.projects = this.projects.filter(p => p.id !== projectId);
this.aircrafts = this.aircrafts.filter(a => a.projectId !== projectId);
const aircraftIds = this.aircrafts.filter(a => a.projectId === projectId).map(a => a.id);
this.containers = this.containers.filter(c => !aircraftIds.includes(c.aircraftId));
const containerIds = this.containers.filter(c => aircraftIds.includes(c.aircraftId)).map(c => c.id);
this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId));
this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId));
this.projects.push(...data.projects);
this.aircrafts.push(...data.aircrafts);
this.containers.push(...data.containers);
this.configs.push(...data.configs);
this.moduleFolders.push(...data.moduleFolders);
this.currentProjectId = projectId;
this.projectPaths.set(projectId, projectPath);
this.currentView = 'aircrafts';
}
vscode.window.showInformationMessage(`项目数据已从 ${projectPath} 加载,包含 ${data.moduleFolders.length} 个模块文件夹`);
this.updateWebview();
return true;
}
catch (error) {
vscode.window.showErrorMessage(`加载项目数据失败: ${error}`);
return false;
}
}
async checkProjectPathHasData(projectPath) {
try {
const dataUri = vscode.Uri.joinPath(vscode.Uri.file(projectPath), '.dcsp-data.json');
await vscode.workspace.fs.stat(dataUri);
return true;
}
catch {
return false;
}
}
// =============================================
// 项目路径选择方法
// =============================================
async openExistingProject() {
try {
const result = await vscode.window.showOpenDialog({
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
openLabel: '选择项目文件夹',
title: '选择包含项目数据的文件夹'
});
if (result && result.length > 0) {
const selectedPath = result[0].fsPath;
await this.loadProjectData(selectedPath);
}
}
catch (error) {
vscode.window.showErrorMessage(`打开项目时出错: ${error}`);
}
}
async selectProjectPath(projectId, projectName) {
try {
const choice = await vscode.window.showQuickPick([
{
label: '$(folder) 选择现有文件夹',
description: '从文件系统中选择已存在的文件夹',
value: 'select'
},
{
label: '$(new-folder) 创建新文件夹',
description: '输入新文件夹路径(将自动创建)',
value: 'create'
}
], {
placeHolder: '选择项目存储方式'
});
if (!choice) {
return null;
}
if (choice.value === 'select') {
return await this.selectExistingProjectPath(projectId, projectName);
}
else {
return await this.createNewProjectPath(projectId, projectName);
}
}
catch (error) {
vscode.window.showErrorMessage(`选择存储路径时出错: ${error}`);
return null;
}
}
async selectExistingProjectPath(projectId, projectName) {
const result = await vscode.window.showOpenDialog({
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
openLabel: `选择 ${projectName} 的存储位置`,
title: `为项目 "${projectName}" 选择存储文件夹`
});
if (result && result.length > 0) {
const selectedPath = result[0].fsPath;
const hasExistingData = await this.checkProjectPathHasData(selectedPath);
if (hasExistingData) {
const loadChoice = await vscode.window.showWarningMessage(`在路径 ${selectedPath} 中检测到现有项目数据,是否加载?`, { modal: true }, '是,加载现有数据', '否,创建新项目');
if (loadChoice === '是,加载现有数据') {
const success = await this.loadProjectData(selectedPath);
if (success) {
this.projectPaths.set(projectId, selectedPath);
this.currentView = 'aircrafts';
this.currentProjectId = projectId;
this.updateWebview();
vscode.window.showInformationMessage(`项目数据已从 ${selectedPath} 加载`);
return selectedPath;
}
}
}
this.projectPaths.set(projectId, selectedPath);
vscode.window.showInformationMessage(`项目存储位置已设置: ${selectedPath}`);
await this.saveCurrentProjectData();
return selectedPath;
}
return null;
}
async createNewProjectPath(projectId, projectName) {
const pathInput = await vscode.window.showInputBox({
prompt: '请输入项目存储路径(绝对路径)',
placeHolder: `/path/to/your/project/${projectName}`,
validateInput: (value) => {
if (!value) {
return '路径不能为空';
}
return null;
}
});
if (pathInput) {
try {
const dirUri = vscode.Uri.file(pathInput);
await vscode.workspace.fs.createDirectory(dirUri);
this.projectPaths.set(projectId, pathInput);
vscode.window.showInformationMessage(`项目存储位置已创建: ${pathInput}`);
await this.saveCurrentProjectData();
return pathInput;
}
catch (error) {
vscode.window.showErrorMessage(`创建目录失败: ${error}`);
return null;
}
}
return null;
}
// =============================================
// 文件树和模块文件夹方法
// =============================================
async loadModuleFolderFileTree(folderId) {
if (this.isWebviewDisposed) {
console.log('⚠️ Webview 已被销毁,跳过文件树加载');
return;
}
const folder = this.moduleFolders.find(f => f.id === folderId);
if (!folder)
return;
try {
this.panel.webview.postMessage({
type: 'moduleFolderLoading',
loading: true
});
}
catch (error) {
console.log('⚠️ 无法发送加载消息Webview 可能已被销毁');
return;
}
try {
const fullPath = this.getModuleFolderFullPath(folder);
if (fullPath) {
const fileTree = await this.buildFileTree(fullPath);
this.currentModuleFolderFileTree = fileTree;
}
}
catch (error) {
console.error('加载模块文件夹文件树失败:', error);
this.currentModuleFolderFileTree = [];
}
if (this.isWebviewDisposed) {
console.log('⚠️ Webview 已被销毁,跳过完成通知');
return;
}
try {
this.panel.webview.postMessage({
type: 'moduleFolderLoading',
loading: false
});
this.updateWebview();
}
catch (error) {
console.log('⚠️ 无法发送完成消息Webview 可能已被销毁');
}
}
async buildFileTree(dir, relativePath = '') {
try {
const files = await fs.promises.readdir(dir);
const tree = [];
for (const file of files) {
if (file.startsWith('.') && file !== '.git')
continue;
if (file === '.dcsp-data.json')
continue;
const filePath = path.join(dir, file);
const stats = await fs.promises.stat(filePath);
const currentRelativePath = path.join(relativePath, file);
if (stats.isDirectory()) {
const children = await this.buildFileTree(filePath, currentRelativePath);
tree.push({
name: file,
type: 'folder',
path: currentRelativePath,
children: children
});
}
else {
tree.push({
name: file,
type: 'file',
path: currentRelativePath
});
}
}
return tree;
}
catch (error) {
console.error('构建文件树失败:', error);
return [];
}
}
// =============================================
// 工具方法
// =============================================
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;
}
generateModuleFolderName(url, branch) {
const repoName = url.split('/').pop()?.replace('.git', '') || 'unknown-repo';
const branchSafeName = branch.replace(/[^a-zA-Z0-9-_]/g, '-');
return {
displayName: repoName,
folderName: branchSafeName
};
}
getModuleFolderFullPath(folder) {
const container = this.containers.find(c => c.id === folder.containerId);
if (!container)
return null;
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
if (!aircraft)
return null;
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!projectPath)
return null;
const pathParts = folder.localPath.split('/').filter(part => part);
if (pathParts.length < 4)
return null;
const folderName = pathParts[pathParts.length - 1];
return path.join(projectPath, aircraft.name, container.name, folderName);
}
async openConfigFileInVSCode(configId) {
const config = this.configs.find(c => c.id === configId);
if (!config) {
vscode.window.showErrorMessage('未找到配置文件');
return;
}
const container = this.containers.find(c => c.id === config.containerId);
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
const projectPath = this.projectPaths.get(aircraft.projectId);
if (!container || !aircraft || !projectPath) {
vscode.window.showErrorMessage('未设置项目存储路径');
return;
}
const filePath = path.join(projectPath, aircraft.name, container.name, config.fileName);
try {
if (!fs.existsSync(filePath)) {
vscode.window.showWarningMessage('配置文件不存在,将创建新文件');
const dirPath = path.dirname(filePath);
await fs.promises.mkdir(dirPath, { recursive: true });
await fs.promises.writeFile(filePath, config.content || '');
}
const document = await vscode.workspace.openTextDocument(filePath);
await vscode.window.showTextDocument(document);
}
catch (error) {
vscode.window.showErrorMessage(`打开配置文件失败: ${error}`);
}
}
async openTheModuleFolder(type, id) {
const folder = this.moduleFolders.find(f => f.id === id);
if (!folder) {
vscode.window.showErrorMessage('未找到指定的模块文件夹');
return;
}
try {
const fullPath = this.getModuleFolderFullPath(folder);
if (!fullPath || !fs.existsSync(fullPath)) {
vscode.window.showErrorMessage('模块文件夹目录不存在');
return;
}
const fileUri = await vscode.window.showOpenDialog({
defaultUri: vscode.Uri.file(fullPath),
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
openLabel: '选择要打开的文件',
title: `${folder.name} 中选择文件`
});
if (fileUri && fileUri.length > 0) {
const document = await vscode.workspace.openTextDocument(fileUri[0]);
await vscode.window.showTextDocument(document);
vscode.window.showInformationMessage(`已打开文件: ${path.basename(fileUri[0].fsPath)}`);
}
}
catch (error) {
vscode.window.showErrorMessage(`打开模块文件夹文件失败: ${error}`);
}
}
async renameModuleFolder(folderId, newName) {
const folder = this.moduleFolders.find(f => f.id === folderId);
if (!folder) {
vscode.window.showErrorMessage('未找到模块文件夹');
return;
}
const oldName = folder.localPath.split('/').pop();
if (!oldName)
return;
const fullPath = this.getModuleFolderFullPath(folder);
if (!fullPath)
return;
const newFullPath = path.join(path.dirname(fullPath), newName);
try {
await fs.promises.rename(fullPath, newFullPath);
// 更新 localPath
folder.localPath = folder.localPath.replace(/\/[^/]+$/, '/' + newName);
await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`已重命名文件夹: ${oldName}${newName}`);
this.updateWebview();
}
catch (error) {
vscode.window.showErrorMessage('重命名失败: ' + error);
}
}
// =============================================
// Webview 更新方法
// =============================================
updateWebview() {
if (this.isWebviewDisposed) {
console.log('⚠️ Webview 已被销毁,跳过更新');
return;
}
try {
this.panel.webview.html = this.getWebviewContent();
}
catch (error) {
console.error('更新 Webview 失败:', error);
}
}
getWebviewContent() {
switch (this.currentView) {
case 'projects':
return this.projectView.render({
projects: this.projects,
projectPaths: this.projectPaths
});
case 'aircrafts':
const projectAircrafts = this.aircrafts.filter(a => a.projectId === this.currentProjectId);
return this.aircraftView.render({
aircrafts: projectAircrafts
});
case 'containers':
const currentProject = this.projects.find(p => p.id === this.currentProjectId);
const currentAircraft = this.aircrafts.find(a => a.id === this.currentAircraftId);
const projectContainers = this.containers.filter(c => c.aircraftId === this.currentAircraftId);
return this.containerView.render({
project: currentProject,
aircraft: currentAircraft,
containers: projectContainers
});
case 'configs':
const currentContainer = this.containers.find(c => c.id === this.currentContainerId);
const currentModuleFolder = this.moduleFolders.find(f => f.id === this.currentModuleFolderId);
const containerConfigs = this.configs.filter(cfg => cfg.containerId === this.currentContainerId);
const containerModuleFolders = this.moduleFolders.filter(folder => folder.containerId === this.currentContainerId);
return this.configView.render({
container: currentContainer,
configs: containerConfigs,
moduleFolders: containerModuleFolders,
currentModuleFolder: currentModuleFolder,
moduleFolderFileTree: this.currentModuleFolderFileTree,
moduleFolderLoading: false
});
default:
return this.projectView.render({
projects: this.projects,
projectPaths: this.projectPaths
});
}
}
}
exports.ConfigPanel = ConfigPanel;
//# sourceMappingURL=ConfigPanel.js.map