0
0
Files
vs-p/out/panels/ConfigPanel.js

1740 lines
79 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";
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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigPanel = void 0;
const vscode = __importStar(require("vscode"));
const path = __importStar(require("path"));
const ProjectView_1 = require("./views/ProjectView");
const AircraftView_1 = require("./views/AircraftView");
const ContainerView_1 = require("./views/ContainerView");
const ConfigView_1 = require("./views/ConfigView");
const ProjectService_1 = require("./services/ProjectService");
const GitService_1 = require("./services/GitService");
const StorageService_1 = require("./services/StorageService");
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.repoConfigs = [];
this.currentCloneScope = 'config';
// 状态管理
this.isWebviewDisposed = false;
this.currentModuleFolderFileTree = [];
this.panel = panel;
this.extensionUri = extensionUri;
this.isWebviewDisposed = false;
// 初始化服务
this.projectService = new ProjectService_1.ProjectService();
// 初始化视图
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;
});
}
// =============================================
// 仓库配置相关
// =============================================
async loadRepoConfigs() {
this.repoConfigs = await StorageService_1.StorageService.loadRepoConfigs(this.extensionUri);
}
async openRepoConfig() {
await StorageService_1.StorageService.openRepoConfig(this.extensionUri);
await this.loadRepoConfigs();
}
/**
* 按作用域弹出仓库选择弹窗(仅用于“获取仓库 -> 获取分支”)
*/
async openRepoSelectForScope(scope) {
this.currentCloneScope = scope;
await this.loadRepoConfigs();
if (this.repoConfigs.length === 0) {
vscode.window.showWarningMessage('尚未配置任何仓库,请先点击右上角 "仓库配置" 按钮编辑 dcsp-repos.json。');
return;
}
if (this.isWebviewDisposed)
return;
this.panel.webview.postMessage({
type: 'showRepoSelect',
repos: this.repoConfigs.map(r => ({ name: r.name }))
});
}
/**
* ConfigView 使用的默认版本(保持兼容)
*/
async openRepoSelect() {
return this.openRepoSelectForScope('config');
}
/**
* 弹出“上传代码”时的仓库 + 分支选择弹窗 (模块文件夹专用)
*/
async openUploadRepoSelect(folderId, folderType) {
await this.loadRepoConfigs();
if (this.repoConfigs.length === 0) {
vscode.window.showWarningMessage('尚未配置任何仓库,请先点击右上角 "仓库配置" 按钮编辑 dcsp-repos.json。');
return;
}
if (this.isWebviewDisposed)
return;
this.panel.webview.postMessage({
type: 'showUploadRepoSelect',
repos: this.repoConfigs.map(r => ({ name: r.name })),
folderId,
folderType
});
}
/**
* 弹出“上传代码”时的仓库 + 分支选择弹窗 (Project/Aircraft/Container 专用)
*/
async openUploadRepoSelectForScope(scope, id) {
await this.loadRepoConfigs();
if (this.repoConfigs.length === 0) {
vscode.window.showWarningMessage('尚未配置任何仓库,请先点击右上角 "仓库配置" 按钮编辑 dcsp-repos.json。');
return;
}
if (this.isWebviewDisposed)
return;
this.panel.webview.postMessage({
type: 'showUploadRepoSelect',
repos: this.repoConfigs.map(r => ({ name: r.name })),
folderId: id,
folderType: scope // 传递 scope 作为类型
});
}
/**
* “获取仓库”弹窗确认后:用于拉取分支
*/
async handleRepoSelectedForBranches(repoName) {
await this.loadRepoConfigs();
const repo = this.repoConfigs.find(r => r.name === repoName);
if (!repo) {
vscode.window.showErrorMessage(`在仓库配置中未找到名为 "${repoName}" 的仓库`);
return;
}
this.currentRepoForBranches = repo;
await this.fetchBranchesForRepo(repo);
}
/**
* “上传代码”弹窗确认后:根据 type 决定上传逻辑,并使用用户输入的 branchName
*/
async handleUploadRepoSelected(id, // 现在是通用 ID (folderId, projectId, aircraftId, containerId)
type, // 扩展类型
repoName, branchName) {
const trimmedBranch = (branchName || '').trim();
if (!trimmedBranch) {
vscode.window.showErrorMessage('分支名称不能为空');
return;
}
await this.loadRepoConfigs();
const repo = this.repoConfigs.find(r => r.name === repoName);
if (!repo) {
vscode.window.showErrorMessage(`在仓库配置中未找到名为 "${repoName}" 的仓库`);
return;
}
if (type === 'local') {
// 本地模块 -> 选中仓库 + 指定分支 (原逻辑)
await this.uploadLocalModuleFolder(id, repo.url, trimmedBranch, repo.username, repo.token);
}
else if (type === 'git') {
// Git 模块 -> 选中仓库 + 指定分支 (原逻辑)
await this.processGitUploadWithBranch(id, repo, trimmedBranch);
}
else {
// 新增逻辑Project, Aircraft, Container 上传
await this.uploadProjectAircraftContainer(id, type, repo, trimmedBranch);
}
}
/**
* Git 模块上传(可能换仓库 / 改名),使用用户指定的分支
*/
async processGitUploadWithBranch(folderId, repo, branchName) {
const folder = this.projectService.getModuleFolder(folderId);
if (!folder || folder.type !== 'git') {
vscode.window.showErrorMessage("未找到要上传的 Git 模块文件夹");
return;
}
const newFolderName = folder.localPath.split('/').pop() ?? '';
const oldFolderName = folder.originalFolderName ?? newFolderName;
const isRenamed = newFolderName !== oldFolderName;
const isSameRepo = !!folder.originalRepoUrl && folder.originalRepoUrl === repo.url;
const fullPath = this.projectService.getModuleFolderFullPath(folder);
if (!fullPath) {
vscode.window.showErrorMessage("无法确定 Git 模块文件夹路径");
return;
}
// 未改名 + 还是原来的仓库 → 直接在原分支上 push更新代码
if (!isRenamed && isSameRepo) {
await this.uploadGitModuleFolder(folderId);
return;
}
// 改了名字 或 换了仓库 → 使用“用户输入的分支名”进行推送
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `正在上传 Git 仓库: ${folder.name}`,
cancellable: false
}, async (progress) => {
try {
progress.report({ increment: 0, message: '准备上传...' });
// 这里使用 GitService.pushToRepoUrl它负责 checkout 和 push
await GitService_1.GitService.pushToRepoUrl(fullPath, repo.url, branchName, repo.username, repo.token);
this.projectService.updateModuleFolder(folderId, {
uploaded: true,
originalFolderName: newFolderName,
originalRepoUrl: repo.url
});
await this.saveCurrentProjectData();
progress.report({ increment: 100, message: '完成' });
vscode.window.showInformationMessage(`✅ Git 仓库已上传到 ${repo.name} 的分支 ${branchName}`);
this.updateWebview();
}
catch (error) {
console.error('❌ Git 上传到新仓库/分支失败:', error);
vscode.window.showErrorMessage(`推送失败: ${error.message || error}`);
}
});
}
// =============================================
// Webview 消息处理
// =============================================
setupMessageListener() {
this.panel.webview.onDidReceiveMessage(async (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(),
// 项目管理 (MODIFIED)
'updateProjectName': (data) => this.updateProjectName(data.projectId, data.name),
'createProject': (data) => this.createProject(data.name),
'deleteProject': (data) => this.deleteProject(data.projectId),
// 飞行器管理 (MODIFIED)
'updateAircraftName': (data) => this.updateAircraftName(data.aircraftId, data.name),
'createAircraft': (data) => this.createAircraft(data.name),
'deleteAircraft': (data) => this.deleteAircraft(data.aircraftId),
// 容器管理 (MODIFIED)
'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(),
'openRepoSelectForProject': () => this.openRepoSelectForScope('project'),
'openRepoSelectForAircraft': () => this.openRepoSelectForScope('aircraft'),
'openRepoSelectForContainer': () => this.openRepoSelectForScope('container'),
'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.openUploadRepoSelect(data.folderId, 'local'),
'uploadLocalModuleFolder': (data) => this.uploadLocalModuleFolder(data.folderId, data.repoUrl, data.branchName),
'openRepoSelectForGitUpload': (data) => this.openUploadRepoSelect(data.folderId, 'git'),
// 新增Project/Aircraft/Container 上传
'openRepoSelectForProjectUpload': (data) => this.openUploadRepoSelectForScope('project', data.projectId),
'openRepoSelectForAircraftUpload': (data) => this.openUploadRepoSelectForScope('aircraft', data.aircraftId),
'openRepoSelectForContainerUpload': (data) => this.openUploadRepoSelectForScope('container', data.containerId),
// 统一的上传时 仓库+分支 选择确认
'uploadRepoSelected': (data) => this.handleUploadRepoSelected(data.folderId, data.folderType, // 此时 data.folderType 可能是 'project' | 'aircraft' | 'container'
data.repoName, 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();
}
// =============================================
// 项目管理方法 (MODIFIED)
// =============================================
/**
* 重命名项目更新内存数据、重命名磁盘文件夹、更新项目路径MODIFIED
*/
async updateProjectName(projectId, newName) {
const project = this.projectService.getProjects().find(p => p.id === projectId);
if (!project)
return;
const paths = this.projectService.getProjectOldAndNewPaths(projectId, newName);
if (!paths) {
vscode.window.showErrorMessage('无法获取项目路径');
return;
}
try {
// 1. 重命名磁盘文件夹
await this.projectService.renameDirectoryOnDisk(paths.oldPath, paths.newPath);
// 2. 更新内存中的项目名称和项目路径
if (this.projectService.updateProjectName(projectId, newName)) {
// 必须更新 projectPaths map
this.projectService.setProjectPath(projectId, paths.newPath);
// 3. 保存数据到新的项目路径下的 .dcsp-data.json
await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`✅ 项目名称和文件夹已更新: ${newName}`);
this.updateWebview();
}
}
catch (error) {
vscode.window.showErrorMessage(`❌ 重命名项目文件夹失败: ${error}`);
}
}
async createProject(name) {
const newId = await this.projectService.createProject(name);
this.currentProjectId = newId;
vscode.window.showInformationMessage(`新建项目: ${name}`);
this.updateWebview();
}
/**
* 删除项目,同时删除磁盘上的文件夹
*/
async deleteProject(projectId) {
const project = this.projectService.getProjects().find(p => p.id === projectId);
// 获取项目存储路径
const projectPath = this.projectService.getProjectPath(projectId);
if (!project)
return;
const confirm = await vscode.window.showWarningMessage(`确定要删除项目 "${project.name}" 吗?这将同时删除磁盘上的文件夹及其所有内容。`, { modal: true }, '确定删除', '取消');
if (confirm !== '确定删除') {
return;
}
try {
// 1. 删除磁盘上的文件夹
if (projectPath) {
await this.projectService.deleteDirectoryFromDisk(projectPath);
}
// 2. 从内存中删除 (同时会清理所有子数据)
if (this.projectService.deleteProject(projectId)) {
// 如果删除的是当前正在查看的项目,则返回 Projects 视图
if (this.currentProjectId === projectId) {
this.currentProjectId = '';
this.currentView = 'projects';
}
vscode.window.showInformationMessage(`项目已删除: ${project.name}`);
// 如果还有其他项目在内存中,保存一下最新的数据状态
if (this.currentProjectId) {
await this.saveCurrentProjectData();
}
this.updateWebview();
}
}
catch (error) {
vscode.window.showErrorMessage(`删除项目文件夹失败: ${error}`);
}
}
// =============================================
// 飞行器管理方法 (MODIFIED)
// =============================================
/**
* 重命名飞行器更新内存数据、重命名磁盘文件夹MODIFIED
*/
async updateAircraftName(aircraftId, newName) {
const aircraft = this.projectService.getAircraft(aircraftId);
if (!aircraft)
return;
const paths = this.projectService.getAircraftOldAndNewPaths(aircraftId, newName);
if (!paths) {
vscode.window.showErrorMessage('无法获取飞行器路径');
return;
}
try {
// 1. 重命名磁盘文件夹
await this.projectService.renameDirectoryOnDisk(paths.oldPath, paths.newPath);
// 2. 更新内存中的飞行器名称
if (this.projectService.updateAircraftName(aircraftId, newName)) {
// 3. 保存数据到 .dcsp-data.json
await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`✅ 飞行器名称和文件夹已更新: ${newName}`);
this.updateWebview();
}
}
catch (error) {
vscode.window.showErrorMessage(`❌ 重命名飞行器文件夹失败: ${error}`);
}
}
async createAircraft(name) {
if (!this.currentProjectId) {
vscode.window.showErrorMessage('无法创建飞行器:未找到当前项目');
return;
}
const aircraftId = await this.projectService.createAircraft(name, this.currentProjectId);
vscode.window.showInformationMessage(`新建飞行器: ${name}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
/**
* 删除飞行器,同时删除磁盘上的文件夹
*/
async deleteAircraft(aircraftId) {
const aircraft = this.projectService.getAircraftsByProject(this.currentProjectId)
.find(a => a.id === aircraftId);
if (!aircraft)
return;
const confirm = await vscode.window.showWarningMessage(`确定要删除飞行器 "${aircraft.name}" 吗?这将同时删除磁盘上的文件夹。`, { modal: true }, '确定删除', '取消');
if (confirm !== '确定删除') {
return;
}
const dirPath = this.projectService.getAircraftDirectoryPath(aircraftId);
try {
// 1. 删除磁盘上的文件夹
if (dirPath) {
await this.projectService.deleteDirectoryFromDisk(dirPath);
}
// 2. 从内存中删除 (同时会清理所有子数据)
if (this.projectService.deleteAircraft(aircraftId)) {
vscode.window.showInformationMessage(`飞行器已删除: ${aircraft.name}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
}
catch (error) {
vscode.window.showErrorMessage(`删除飞行器文件夹失败: ${error}`);
}
}
// =============================================
// 容器管理方法 (MODIFIED)
// =============================================
/**
* 重命名容器更新内存数据、重命名磁盘文件夹MODIFIED
*/
async updateContainerName(containerId, newName) {
const container = this.projectService.getContainer(containerId);
if (!container)
return;
const paths = this.projectService.getContainerOldAndNewPaths(containerId, newName);
if (!paths) {
vscode.window.showErrorMessage('无法获取容器路径');
return;
}
try {
// 1. 重命名磁盘文件夹
await this.projectService.renameDirectoryOnDisk(paths.oldPath, paths.newPath);
// 2. 更新内存中的容器名称
if (this.projectService.updateContainerName(containerId, newName)) {
// 3. 保存数据到 .dcsp-data.json
await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`✅ 容器名称和文件夹已更新: ${newName}`);
this.updateWebview();
}
}
catch (error) {
vscode.window.showErrorMessage(`❌ 重命名容器文件夹失败: ${error}`);
}
}
async createContainer(name) {
if (!this.currentAircraftId) {
vscode.window.showErrorMessage('无法创建容器:未找到当前飞行器');
return;
}
const containerId = await this.projectService.createContainer(name, this.currentAircraftId);
vscode.window.showInformationMessage(`新建容器: ${name} (包含2个默认配置文件)`);
await this.saveCurrentProjectData();
this.updateWebview();
}
/**
* 删除容器,同时删除磁盘上的文件夹
*/
async deleteContainer(containerId) {
const container = this.projectService.getContainersByAircraft(this.currentAircraftId)
.find(c => c.id === containerId);
if (!container)
return;
const confirm = await vscode.window.showWarningMessage(`确定要删除容器 "${container.name}" 吗?这将同时删除磁盘上的文件夹。`, { modal: true }, '确定删除', '取消');
if (confirm !== '确定删除') {
return;
}
const dirPath = this.projectService.getContainerDirectoryPath(containerId);
try {
// 1. 删除磁盘上的文件夹
if (dirPath) {
await this.projectService.deleteDirectoryFromDisk(dirPath);
}
// 2. 从内存中删除 (同时会清理所有子数据)
if (this.projectService.deleteContainer(containerId)) {
vscode.window.showInformationMessage(`容器已删除: ${container.name}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
}
catch (error) {
vscode.window.showErrorMessage(`删除容器文件夹失败: ${error}`);
}
}
// =============================================
// 配置管理方法
// =============================================
async updateConfigName(configId, newName) {
if (this.projectService.updateConfigName(configId, newName)) {
vscode.window.showInformationMessage(`配置名称更新: ${newName}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
}
async createConfig(name) {
// 注意:真正创建文件内容的逻辑放在 ProjectService.createConfig 里处理
const configId = await this.projectService.createConfig(name, this.currentContainerId);
vscode.window.showInformationMessage(`新建配置: ${name}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
async deleteConfig(configId) {
const config = this.projectService.getConfig(configId);
if (!config)
return;
const confirm = await vscode.window.showWarningMessage(`确定要删除配置文件 "${config.name}" 吗?这将同时删除磁盘上的文件。`, { modal: true }, '确定删除', '取消');
if (confirm !== '确定删除') {
return;
}
try {
// 删除配置文件(磁盘)
await this.projectService.deleteConfigFileFromDisk(configId);
// 从内存中删除
this.projectService.deleteConfig(configId);
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.projectService.getContainersByAircraft(this.currentAircraftId)
.find(c => c.id === this.currentContainerId);
if (!container) {
vscode.window.showErrorMessage('未找到容器数据');
return;
}
const projectPath = this.projectService.getProjectPath(this.currentProjectId);
if (!projectPath) {
vscode.window.showErrorMessage('未找到项目路径');
return;
}
// 获取选中的配置(仅元信息,不含 content
const selectedConfigs = configIds
.map(id => this.projectService.getConfig(id))
.filter(Boolean);
if (selectedConfigs.length !== configIds.length) {
vscode.window.showErrorMessage('部分配置文件未找到');
return;
}
// 创建合并文件夹
const mergeFolderPath = path.join(projectPath, this.getAircraftName(), container.name, folderName);
await vscode.workspace.fs.createDirectory(vscode.Uri.file(mergeFolderPath));
const fs = require('fs');
// 复制文件到合并文件夹:完全基于磁盘文件
for (const config of selectedConfigs) {
if (!config)
continue;
const sourcePath = this.projectService.getConfigFilePath(config.id);
const targetPath = path.join(mergeFolderPath, config.fileName);
if (sourcePath && fs.existsSync(sourcePath)) {
await fs.promises.copyFile(sourcePath, targetPath);
}
else {
// 找不到源文件:写一个占位注释文件
const placeholder = `# 原配置文件 "${config.fileName}" 未在磁盘中找到\n` +
`# 仅保留占位文件,建议手动补充内容\n\n`;
await fs.promises.writeFile(targetPath, placeholder, 'utf8');
}
}
// 创建模块文件夹记录(使用 ProjectService 的统一 ID 生成)
const relativePath = `/${this.currentProjectId}/${this.getAircraftName()}/${container.name}/${folderName}`;
const newFolder = {
id: this.projectService.generateUniqueId('local-'),
name: displayName,
type: 'local',
localPath: relativePath,
containerId: this.currentContainerId
};
this.projectService.addModuleFolder(newFolder);
// 删除原配置(包括磁盘上的配置文件)
for (const configId of configIds) {
await this.projectService.deleteConfigFileFromDisk(configId);
this.projectService.deleteConfig(configId);
}
await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`成功合并 ${selectedConfigs.length} 个配置文件到文件夹: ${folderName}`);
this.updateWebview();
}
catch (error) {
console.error('❌ 合并配置文件失败:', error);
vscode.window.showErrorMessage(`合并配置文件失败: ${error}`);
}
}
getAircraftName() {
const aircrafts = this.projectService.getAircraftsByProject(this.currentProjectId);
const aircraft = aircrafts.find(a => a.id === this.currentAircraftId);
return aircraft?.name || '未知飞行器';
}
getContainerName() {
const containers = this.projectService.getContainersByAircraft(this.currentAircraftId);
const container = containers.find(c => c.id === this.currentContainerId);
return container?.name || '未知容器';
}
// =============================================
// Git 分支管理方法
// =============================================
async fetchBranchesForRepo(repo) {
await this.fetchBranches(repo.url, repo);
}
async fetchBranches(url, repo) {
try {
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 branches = await GitService_1.GitService.fetchBranches(url, repo?.username, repo?.token);
if (branches.length === 0) {
throw new Error('未找到任何分支');
}
progress.report({ increment: 80, message: '处理分支数据...' });
const branchTree = GitService_1.GitService.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('❌ 获取分支失败:', error);
vscode.window.showErrorMessage(`获取分支失败: ${error}`);
}
});
}
catch (error) {
console.error('❌ 获取分支失败:', error);
vscode.window.showErrorMessage(`获取分支失败: ${error}`);
}
}
/**
* 根据当前作用域,把选中的分支克隆到不同层级
*/
async cloneBranches(branches) {
if (!this.currentRepoForBranches) {
vscode.window.showErrorMessage('请先通过"获取仓库"选择一个仓库');
return;
}
switch (this.currentCloneScope) {
case 'project':
await this.cloneBranchesToProjects(branches);
break;
case 'aircraft':
await this.cloneBranchesToAircrafts(branches);
break;
case 'container':
await this.cloneBranchesToContainers(branches);
break;
case 'config':
default:
await this.cloneBranchesToModuleFolders(branches);
break;
}
}
/**
* 原有逻辑:在 ConfigView 中,把分支克隆为当前容器下的 git 模块文件夹
*/
async cloneBranchesToModuleFolders(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 = GitService_1.GitService.generateModuleFolderName(url, branch);
await this.addGitModuleFolder(url, repoDisplayName, folderNames.folderName, branch, this.currentRepoForBranches?.username, this.currentRepoForBranches?.token);
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}`);
}
}
/**
* ProjectView 用:把分支克隆为完整项目
* 每个分支会弹出一个路径输入框,克隆整个仓库到该路径,如果有 .dcsp-data.json 则自动加载为项目
*/
async cloneBranchesToProjects(branches) {
if (!this.currentRepoForBranches) {
vscode.window.showErrorMessage('请先通过"获取仓库"选择一个仓库');
return;
}
const url = this.currentRepoForBranches.url;
const tasks = [];
for (const branch of branches) {
const pathInput = await vscode.window.showInputBox({
prompt: `请输入分支 "${branch}" 的项目存储路径(绝对路径,系统将自动创建该文件夹)`,
placeHolder: `/path/to/your/project/${branch}`,
validateInput: (value) => {
if (!value)
return '路径不能为空';
return null;
}
});
if (!pathInput) {
vscode.window.showWarningMessage(`已取消分支 ${branch} 的克隆`);
continue;
}
tasks.push({ branch, targetPath: pathInput });
}
if (tasks.length === 0) {
return;
}
let successCount = 0;
let failCount = 0;
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `正在克隆 ${tasks.length} 个项目分支`,
cancellable: false
}, async (progress) => {
for (let i = 0; i < tasks.length; i++) {
const { branch, targetPath } = tasks[i];
progress.report({
message: `克隆项目分支: ${branch} (${i + 1}/${tasks.length})`
});
try {
await GitService_1.GitService.cloneRepository(url, targetPath, branch, undefined, this.currentRepoForBranches?.username, this.currentRepoForBranches?.token);
// 克隆完成后,尝试加载项目数据(如果有 .dcsp-data.json
try {
const projectId = await this.projectService.loadProjectData(targetPath);
if (projectId) {
this.currentProjectId = projectId;
successCount++;
}
else {
// 没有 dscp-data 也算成功克隆,只是不会自动出现在列表里
successCount++;
}
}
catch (err) {
console.warn('加载克隆项目数据失败:', err);
successCount++;
}
}
catch (error) {
console.error(`❌ 项目分支克隆失败: ${branch}`, error);
failCount++;
}
}
});
await vscode.commands.executeCommand('workbench.files.action.refreshFilesExplorer');
if (failCount === 0) {
vscode.window.showInformationMessage(`成功克隆 ${successCount} 个项目分支`);
}
else {
vscode.window.showWarningMessage(`项目克隆完成: ${successCount} 个成功, ${failCount} 个失败`);
}
this.currentView = 'projects';
this.updateWebview();
}
/**
* AircraftView 用:把分支克隆为当前项目下的飞行器
* 目标路径:<projectPath>/<分支名>,克隆后自动解析容器/配置/模块,并写入 dscp-data.json
*/
async cloneBranchesToAircrafts(branches) {
if (!this.currentRepoForBranches) {
vscode.window.showErrorMessage('请先通过"获取仓库"选择一个仓库');
return;
}
if (!this.currentProjectId) {
vscode.window.showErrorMessage('请先选择项目');
return;
}
const projectPath = this.projectService.getProjectPath(this.currentProjectId);
if (!projectPath) {
vscode.window.showErrorMessage('未找到项目路径');
return;
}
const url = this.currentRepoForBranches.url;
const username = this.currentRepoForBranches.username;
const token = this.currentRepoForBranches.token;
let successCount = 0;
let failCount = 0;
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `正在克隆飞行器分支`,
cancellable: false
}, async (progress) => {
for (let i = 0; i < branches.length; i++) {
const branch = branches[i];
const aircraftName = branch.trim();
if (!aircraftName)
continue;
const localPath = path.join(projectPath, aircraftName);
progress.report({
message: `克隆飞行器分支: ${branch} (${i + 1}/${branches.length})`
});
try {
await GitService_1.GitService.cloneRepository(url, localPath, branch, undefined, username, token);
await this.projectService.importAircraftFromExistingFolder(this.currentProjectId, aircraftName);
successCount++;
}
catch (error) {
console.error(`❌ 飞行器分支克隆失败: ${branch}`, error);
failCount++;
}
}
});
await this.saveCurrentProjectData();
await vscode.commands.executeCommand('workbench.files.action.refreshFilesExplorer');
if (failCount === 0) {
vscode.window.showInformationMessage(`成功克隆 ${successCount} 个飞行器`);
}
else {
vscode.window.showWarningMessage(`飞行器克隆完成: ${successCount} 个成功, ${failCount} 个失败`);
}
this.currentView = 'aircrafts';
this.updateWebview();
}
/**
* ContainerView 用:把分支克隆为当前飞行器下的容器
* 目标路径:<projectPath>/<当前飞行器名>/<分支名>,克隆后自动解析配置/模块,并写入 dscp-data.json
*/
async cloneBranchesToContainers(branches) {
if (!this.currentRepoForBranches) {
vscode.window.showErrorMessage('请先通过"获取仓库"选择一个仓库');
return;
}
if (!this.currentProjectId || !this.currentAircraftId) {
vscode.window.showErrorMessage('请先选择项目和飞行器');
return;
}
const projectPath = this.projectService.getProjectPath(this.currentProjectId);
if (!projectPath) {
vscode.window.showErrorMessage('未找到项目路径');
return;
}
const aircraftName = this.getAircraftName();
if (!aircraftName || aircraftName === '未知飞行器') {
vscode.window.showErrorMessage('未找到当前飞行器名称');
return;
}
const url = this.currentRepoForBranches.url;
const username = this.currentRepoForBranches.username;
const token = this.currentRepoForBranches.token;
let successCount = 0;
let failCount = 0;
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `正在克隆容器分支`,
cancellable: false
}, async (progress) => {
for (let i = 0; i < branches.length; i++) {
const branch = branches[i];
const containerName = branch.trim();
if (!containerName)
continue;
const localPath = path.join(projectPath, aircraftName, containerName);
progress.report({
message: `克隆容器分支: ${branch} (${i + 1}/${branches.length})`
});
try {
await GitService_1.GitService.cloneRepository(url, localPath, branch, undefined, username, token);
await this.projectService.importContainerFromExistingFolder(this.currentAircraftId, containerName);
successCount++;
}
catch (error) {
console.error(`❌ 容器分支克隆失败: ${branch}`, error);
failCount++;
}
}
});
await this.saveCurrentProjectData();
await vscode.commands.executeCommand('workbench.files.action.refreshFilesExplorer');
if (failCount === 0) {
vscode.window.showInformationMessage(`成功克隆 ${successCount} 个容器`);
}
else {
vscode.window.showWarningMessage(`容器克隆完成: ${successCount} 个成功, ${failCount} 个失败`);
}
this.currentView = 'containers';
this.updateWebview();
}
async addGitModuleFolder(url, displayName, folderName, branch, username, token) {
try {
if (!url || !url.startsWith('http')) {
vscode.window.showErrorMessage('请输入有效的 Git 仓库 URL');
return;
}
if (!this.currentContainerId) {
vscode.window.showErrorMessage('请先选择容器');
return;
}
const projectPath = this.projectService.getProjectPath(this.currentProjectId);
if (!projectPath) {
vscode.window.showErrorMessage('未找到相关项目数据');
return;
}
// 使用 ProjectService 的统一 ID 生成
const folderId = this.projectService.generateUniqueId('git-');
const relativePath = `/${this.currentProjectId}/${this.getAircraftName()}/${this.getContainerName()}/${folderName}`;
const localPath = path.join(projectPath, this.getAircraftName(), this.getContainerName(), folderName);
console.log(`📁 准备克隆仓库: ${displayName}, 分支: ${branch}, 路径: ${localPath}`);
const existingFolder = this.projectService.getModuleFoldersByContainer(this.currentContainerId)
.find(folder => folder.localPath === relativePath);
if (existingFolder) {
vscode.window.showWarningMessage(`该路径的模块文件夹已存在: ${folderName}`);
return;
}
const newFolder = {
id: folderId,
name: displayName,
type: 'git',
localPath: relativePath,
containerId: this.currentContainerId,
originalFolderName: folderName,
originalRepoUrl: url
};
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `正在克隆仓库: ${displayName}`,
cancellable: false
}, async (progress) => {
progress.report({ increment: 0 });
try {
// 使用 GitService 克隆仓库
await GitService_1.GitService.cloneRepository(url, localPath, branch, (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}...` });
}
}, username, token);
console.log('✅ Git克隆成功完成');
const clonedContents = await require('fs').promises.readdir(localPath);
console.log(`📁 克隆后的目录内容:`, clonedContents);
if (clonedContents.length === 0) {
throw new Error('克隆后目录为空,可能克隆失败');
}
this.projectService.addModuleFolder(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 (require('fs').existsSync(localPath)) {
await require('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.projectService.getModuleFolder(folderId);
if (folder && folder.type === 'git') {
await this.loadModuleFolderFileTree(folderId);
}
this.updateWebview();
}
async syncGitModuleFolder(folderId) {
const folder = this.projectService.getModuleFolder(folderId);
if (!folder || folder.type !== 'git') {
vscode.window.showErrorMessage('未找到指定的 Git 模块文件夹');
return;
}
const fullPath = this.projectService.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 GitService_1.GitService.pullChanges(fullPath);
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.projectService.getModuleFolder(folderId);
if (!folder)
return;
const confirm = await vscode.window.showWarningMessage(`确定要删除模块文件夹 "${folder.name}" 吗?这将删除本地文件。`, { modal: true }, '确定删除', '取消');
if (confirm === '确定删除') {
try {
const fullPath = this.projectService.getModuleFolderFullPath(folder);
if (fullPath) {
await require('fs').promises.rm(fullPath, { recursive: true, force: true });
}
this.projectService.deleteModuleFolder(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.projectService.getModuleFolder(this.currentModuleFolderId);
if (!folder || folder.type !== 'git') {
vscode.window.showErrorMessage('未找到当前 Git 模块文件夹');
return;
}
try {
const fullPath = this.projectService.getModuleFolderFullPath(folder);
if (!fullPath) {
vscode.window.showErrorMessage('无法获取模块文件夹路径');
return;
}
const fs = require('fs');
const sourceFullPath = path.join(fullPath, filePath);
const content = await fs.promises.readFile(sourceFullPath, 'utf8');
const fileName = path.basename(filePath);
// 1. 先创建一个配置ProjectService.createConfig 内部会在磁盘上生成文件)
const configId = await this.projectService.createConfig(fileName, this.currentContainerId);
// 2. 再把 Git 文件的内容写到该配置文件上,覆盖默认模板
const targetConfigPath = this.projectService.getConfigFilePath(configId);
if (!targetConfigPath) {
vscode.window.showErrorMessage('无法获取新配置文件的路径');
return;
}
const dirPath = path.dirname(targetConfigPath);
await fs.promises.mkdir(dirPath, { recursive: true });
await fs.promises.writeFile(targetConfigPath, content, 'utf8');
await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`文件已导入: ${fileName}`);
this.updateWebview();
}
catch (error) {
vscode.window.showErrorMessage(`导入文件失败: ${error}`);
}
}
// =============================================
// 上传功能方法
// =============================================
/**
* 上传 Project / Aircraft / Container 目录到 Git 仓库
*/
async uploadProjectAircraftContainer(id, type, repo, branchName) {
let fullPath = null;
let name = '';
// 1. 获取目标目录的完整路径和名称
if (type === 'project') {
fullPath = this.projectService.getProjectPath(id) || null;
name = this.projectService.getProjects().find(p => p.id === id)?.name || 'Project';
}
else if (type === 'aircraft') {
const aircraft = this.projectService.getAircraftsByProject(this.currentProjectId).find(a => a.id === id);
name = aircraft?.name || 'Aircraft';
if (aircraft) {
fullPath = this.projectService.getAircraftDirectoryPath(id);
}
}
else if (type === 'container') {
const container = this.projectService.getContainersByAircraft(this.currentAircraftId).find(c => c.id === id);
name = container?.name || 'Container';
if (container) {
fullPath = this.projectService.getContainerDirectoryPath(id);
}
}
if (!fullPath || !require('fs').existsSync(fullPath)) {
vscode.window.showErrorMessage(`无法获取 ${type} "${name}" 的有效路径或目录不存在。请先配置项目路径并确保目录存在。`);
return;
}
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `正在上传 ${type}: ${name}`,
cancellable: false
}, async (progress) => {
try {
progress.report({ increment: 0, message: '检查目录...' });
const fs = require('fs');
// 检查目录是否为空
const dirContents = await fs.promises.readdir(fullPath);
if (dirContents.length === 0) {
throw new Error('目录为空,无法上传');
}
// 2. 检查是否为 Git 仓库并初始化
let isGitRepo = false;
try {
// Check for .git directory
if (fs.existsSync(path.join(fullPath, '.git'))) {
isGitRepo = true;
}
}
catch (e) { /* ignore */ }
if (!isGitRepo) {
progress.report({ increment: 10, message: '初始化 Git 仓库...' });
// initRepository 内部会创建并切换到指定分支
await GitService_1.GitService.initRepository(fullPath, branchName);
}
// 3. 配置远程仓库
progress.report({ increment: 20, message: '配置远程仓库...' });
try {
// 尝试移除旧的 origin 远程,以避免冲突
await GitService_1.GitService.removeRemote(fullPath);
}
catch (e) { /* ignore if no remote */ }
await GitService_1.GitService.addRemote(fullPath, repo.url, repo.username, repo.token);
// 4. 提交并推送
progress.report({ increment: 40, message: '提交并推送到远程仓库...' });
// commitAndPushToBranch 内部会 add, commit, checkout -B, push
await GitService_1.GitService.commitAndPushToBranch(fullPath, branchName, repo.url, repo.username, repo.token);
progress.report({ increment: 100, message: '完成' });
vscode.window.showInformationMessage(`${type} ${name} 已成功上传到 ${repo.name} 的分支 ${branchName}`);
this.updateWebview();
}
catch (error) {
console.error(`${type} 上传失败:`, error);
vscode.window.showErrorMessage(`推送失败: ${error.message || error}`);
}
});
}
async uploadGitModuleFolder(folderId, username, password) {
const folder = this.projectService.getModuleFolder(folderId);
if (!folder || folder.type !== 'git') {
vscode.window.showErrorMessage('未找到指定的 Git 模块文件夹');
return;
}
const fullPath = this.projectService.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 GitService_1.GitService.commitAndPush(fullPath);
progress.report({ increment: 100, message: '完成' });
this.projectService.updateModuleFolder(folderId, { 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, username, token) {
const folder = this.projectService.getModuleFolder(folderId);
if (!folder || folder.type !== 'local') {
vscode.window.showErrorMessage('未找到指定的本地模块文件夹');
return;
}
const fullPath = this.projectService.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: '检查目录...' });
const fs = require('fs');
if (!fs.existsSync(fullPath)) {
throw new Error('本地文件夹不存在');
}
progress.report({ increment: 10, message: '初始化 Git 仓库...' });
await GitService_1.GitService.initRepository(fullPath, branchName);
progress.report({ increment: 20, message: '添加远程仓库...' });
await GitService_1.GitService.addRemote(fullPath, repoUrl, username, token);
progress.report({ increment: 40, message: '提交初始文件...' });
await GitService_1.GitService.commitInitialFiles(fullPath);
progress.report({ increment: 60, message: '推送到远程仓库...' });
await GitService_1.GitService.pushToRemote(fullPath, branchName);
progress.report({ increment: 100, message: '完成' });
this.projectService.updateModuleFolder(folderId, {
type: 'git',
uploaded: true,
originalFolderName: branchName,
originalRepoUrl: repoUrl
});
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');
const fs = require('fs');
if (fs.existsSync(gitDir)) {
await fs.promises.rm(gitDir, { recursive: true, force: true });
}
}
catch (cleanupError) {
console.error('清理 .git 文件夹失败:', cleanupError);
}
}
});
}
// =============================================
// 文件树和模块文件夹方法
// =============================================
async loadModuleFolderFileTree(folderId) {
if (this.isWebviewDisposed) {
console.log('⚠️ Webview 已被销毁,跳过文件树加载');
return;
}
const folder = this.projectService.getModuleFolder(folderId);
if (!folder)
return;
try {
this.panel.webview.postMessage({
type: 'moduleFolderLoading',
loading: true
});
}
catch (error) {
console.log('⚠️ 无法发送加载消息Webview 可能已被销毁');
return;
}
try {
const fullPath = this.projectService.getModuleFolderFullPath(folder);
if (fullPath) {
const fileTree = await GitService_1.GitService.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 openConfigFileInVSCode(configId) {
const config = this.projectService.getConfig(configId);
if (!config) {
vscode.window.showErrorMessage('未找到配置文件');
return;
}
const filePath = this.projectService.getConfigFilePath(configId);
if (!filePath) {
vscode.window.showErrorMessage('未设置项目存储路径');
return;
}
try {
const fs = require('fs');
if (!fs.existsSync(filePath)) {
vscode.window.showWarningMessage('配置文件不存在,将创建新文件');
const dirPath = path.dirname(filePath);
await fs.promises.mkdir(dirPath, { recursive: true });
const now = new Date();
const header = `# ${config.fileName} 配置文件\n` +
`# 创建时间: ${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()} ` +
`${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}\n` +
`# 您可以在此编辑配置内容\n\n`;
await fs.promises.writeFile(filePath, header, 'utf8');
}
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.projectService.getModuleFolder(id);
if (!folder) {
vscode.window.showErrorMessage('未找到指定的模块文件夹');
return;
}
try {
const fullPath = this.projectService.getModuleFolderFullPath(folder);
const fs = require('fs');
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.projectService.getModuleFolder(folderId);
if (!folder) {
vscode.window.showErrorMessage('未找到模块文件夹');
return;
}
const oldName = folder.localPath.split('/').pop();
if (!oldName)
return;
const fullPath = this.projectService.getModuleFolderFullPath(folder);
if (!fullPath)
return;
const newFullPath = path.join(path.dirname(fullPath), newName);
try {
const fs = require('fs');
await fs.promises.rename(fullPath, newFullPath);
this.projectService.renameModuleFolder(folderId, newName);
await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`已重命名文件夹: ${oldName}${newName}`);
this.updateWebview();
}
catch (error) {
vscode.window.showErrorMessage('重命名失败: ' + error);
}
}
// =============================================
// 项目路径选择方法
// =============================================
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 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.projectService.setProjectPath(projectId, pathInput); // 保存路径
vscode.window.showInformationMessage(`项目存储位置已创建: ${pathInput}`);
await this.saveCurrentProjectData();
return pathInput;
}
catch (error) {
vscode.window.showErrorMessage(`创建目录失败: ${error}`);
return null;
}
}
return null;
}
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 StorageService_1.StorageService.checkProjectPathHasData(selectedPath);
if (hasExistingData) {
const loadChoice = await vscode.window.showWarningMessage(`在路径 ${selectedPath} 中检测到现有项目数据,是否加载?`, { modal: true }, '是,加载现有数据', '否,创建新项目');
if (loadChoice === '是,加载现有数据') {
const success = await this.loadProjectData(selectedPath);
if (success) {
// loadProjectData 里已经设置好了 currentProjectId 和 projectPath
return selectedPath;
}
}
// 如果选择“否,创建新项目”,就往下走,覆盖旧数据
}
// 没有旧数据,或者选择了“创建新项目”,把当前 projectId 绑定到这个路径
this.projectService.setProjectPath(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.projectService.setProjectPath(projectId, pathInput);
vscode.window.showInformationMessage(`项目存储位置已创建: ${pathInput}`);
await this.saveCurrentProjectData();
return pathInput;
}
catch (error) {
vscode.window.showErrorMessage(`创建目录失败: ${error}`);
return null;
}
}
return null;
}
async loadProjectData(projectPath) {
try {
const projectId = await this.projectService.loadProjectData(projectPath);
if (projectId) {
this.currentProjectId = projectId;
this.currentView = 'aircrafts';
vscode.window.showInformationMessage(`项目数据已从 ${projectPath} 加载`);
this.updateWebview();
return true;
}
return false;
}
catch (error) {
vscode.window.showErrorMessage(`加载项目数据失败: ${error}`);
return false;
}
}
// =============================================
// 数据持久化
// =============================================
async saveCurrentProjectData() {
if (!this.currentProjectId) {
console.warn('未找到当前项目,数据将不会保存');
return;
}
await this.projectService.saveCurrentProjectData(this.currentProjectId);
}
// =============================================
// Webview 更新方法
// =============================================
// =============================================
// 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.projectService.getProjects(),
projectPaths: this.projectService.getProjectPaths()
});
case 'aircrafts':
const projectAircrafts = this.projectService.getAircraftsByProject(this.currentProjectId);
return this.aircraftView.render({
aircrafts: projectAircrafts
});
case 'containers':
const project = this.projectService.getProjects().find(p => p.id === this.currentProjectId);
const currentAircraft = this.projectService.getAircraftsByProject(this.currentProjectId)
.find(a => a.id === this.currentAircraftId);
// ✅ 只在 UI 层隐藏名为 .git 的容器
const projectContainers = this.projectService
.getContainersByAircraft(this.currentAircraftId)
.filter(c => c.name !== '.git');
return this.containerView.render({
project: project,
aircraft: currentAircraft,
containers: projectContainers
});
case 'configs':
const currentContainer = this.projectService.getContainersByAircraft(this.currentAircraftId)
.find(c => c.id === this.currentContainerId);
const currentModuleFolder = this.projectService.getModuleFolder(this.currentModuleFolderId);
const containerConfigs = this.projectService.getConfigsByContainer(this.currentContainerId);
const containerModuleFolders = this.projectService.getModuleFoldersByContainer(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.projectService.getProjects(),
projectPaths: this.projectService.getProjectPaths()
});
}
}
}
exports.ConfigPanel = ConfigPanel;
//# sourceMappingURL=ConfigPanel.js.map