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

1658 lines
76 KiB
JavaScript
Raw Permalink 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;
// src/panels/ConfigPanel.ts
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");
const debounce = (func, wait) => {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
};
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(() => {
if (this.fileWatcher) {
this.fileWatcher.dispose();
}
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 }))
});
}
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
});
}
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
});
}
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);
}
async handleUploadRepoSelected(id, 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') {
await this.processGitUploadWithBranch(id, repo, trimmedBranch);
}
else {
await this.uploadProjectAircraftContainer(id, type, repo, trimmedBranch);
}
}
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;
}
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: '准备上传...' });
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}`);
}
});
}
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(),
'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, data.fileName),
'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),
'openRepoConfig': () => this.openRepoConfig(),
'openRepoSelect': () => this.openRepoSelect(),
'openRepoSelectForProject': () => this.openRepoSelectForScope('project'),
'openRepoSelectForAircraft': () => this.openRepoSelectForScope('aircraft'),
'openRepoSelectForContainer': () => this.openRepoSelectForScope('container'),
'repoSelectedForBranches': (data) => this.handleRepoSelectedForBranches(data.repoName),
'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, data.newFolderName),
'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'),
'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.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) {
// [新增] 在进入视图前,主动扫描磁盘同步数据
const changed = await this.projectService.refreshProjectContent(data.projectId);
if (changed) {
await this.saveCurrentProjectData(); // 如果有变动,立即保存
}
this.currentView = 'aircrafts';
this.currentProjectId = data.projectId;
this.updateWebview();
}
async handleOpenAircraftConfig(data) {
// [新增] 进入飞行器详情前,主动同步容器列表
const changed = await this.projectService.refreshAircraftContent(data.aircraftId);
if (changed) {
await this.saveCurrentProjectData();
}
this.currentView = 'containers';
this.currentProjectId = data.projectId;
this.currentAircraftId = data.aircraftId;
this.updateWebview();
}
async handleOpenContainerConfig(data) {
// [新增] 进入容器详情前,主动同步配置和模块
const changed = await this.projectService.refreshContainerContent(data.containerId);
if (changed) {
await this.saveCurrentProjectData();
}
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.projectService.getProjects().find(p => p.id === projectId);
if (!project)
return;
if (project.name !== newName && this.projectService.isProjectNameExists(newName)) {
vscode.window.showErrorMessage(`❌ 项目名称 "${newName}" 已存在`);
this.updateWebview();
return;
}
const paths = this.projectService.getProjectOldAndNewPaths(projectId, newName);
if (!paths) {
vscode.window.showErrorMessage('无法获取项目路径');
return;
}
try {
await this.projectService.renameDirectoryOnDisk(paths.oldPath, paths.newPath);
if (this.projectService.updateProjectName(projectId, newName)) {
this.projectService.setProjectPath(projectId, paths.newPath);
await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`✅ 项目名称和文件夹已更新: ${newName}`);
this.updateWebview();
}
}
catch (error) {
vscode.window.showErrorMessage(`❌ 重命名项目文件夹失败: ${error}`);
}
}
async createProject(name) {
if (this.projectService.isProjectNameExists(name)) {
vscode.window.showErrorMessage(`❌ 项目名称 "${name}" 已存在`);
return;
}
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 {
if (projectPath) {
await this.projectService.deleteDirectoryFromDisk(projectPath);
}
if (this.projectService.deleteProject(projectId)) {
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}`);
}
}
async updateAircraftName(aircraftId, newName) {
const aircraft = this.projectService.getAircraft(aircraftId);
if (!aircraft)
return;
if (aircraft.name !== newName && this.projectService.isAircraftNameExists(this.currentProjectId, newName)) {
vscode.window.showErrorMessage(`❌ 飞行器名称 "${newName}" 已存在`);
this.updateWebview();
return;
}
const paths = this.projectService.getAircraftOldAndNewPaths(aircraftId, newName);
if (!paths) {
vscode.window.showErrorMessage('无法获取飞行器路径');
return;
}
try {
await this.projectService.renameDirectoryOnDisk(paths.oldPath, paths.newPath);
if (this.projectService.updateAircraftName(aircraftId, newName)) {
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;
}
if (this.projectService.isAircraftNameExists(this.currentProjectId, name)) {
vscode.window.showErrorMessage(`❌ 该项目下已存在名为 "${name}" 的飞行器`);
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 {
if (dirPath) {
await this.projectService.deleteDirectoryFromDisk(dirPath);
}
if (this.projectService.deleteAircraft(aircraftId)) {
vscode.window.showInformationMessage(`飞行器已删除: ${aircraft.name}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
}
catch (error) {
vscode.window.showErrorMessage(`删除飞行器文件夹失败: ${error}`);
}
}
async updateContainerName(containerId, newName) {
const container = this.projectService.getContainer(containerId);
if (!container)
return;
if (container.name !== newName && this.projectService.isContainerNameExists(this.currentAircraftId, newName)) {
vscode.window.showErrorMessage(`❌ 容器名称 "${newName}" 已存在`);
this.updateWebview();
return;
}
const paths = this.projectService.getContainerOldAndNewPaths(containerId, newName);
if (!paths) {
vscode.window.showErrorMessage('无法获取容器路径');
return;
}
try {
await this.projectService.renameDirectoryOnDisk(paths.oldPath, paths.newPath);
if (this.projectService.updateContainerName(containerId, newName)) {
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;
}
if (this.projectService.isContainerNameExists(this.currentAircraftId, name)) {
vscode.window.showErrorMessage(`❌ 该飞行器下已存在名为 "${name}" 的容器`);
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 {
if (dirPath) {
await this.projectService.deleteDirectoryFromDisk(dirPath);
}
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, newFileName) {
const config = this.projectService.getConfig(configId);
if (!config)
return;
const check = this.projectService.isConfigOrFolderConflict(config.containerId, newName, newFileName, configId);
if (check.nameConflict) {
vscode.window.showErrorMessage(`❌ 显示名称 "${newName}" 已占用`);
this.updateWebview();
return;
}
if (check.fileConflict) {
vscode.window.showErrorMessage(`❌ 文件名 "${newFileName}" 已在磁盘中存在`);
this.updateWebview();
return;
}
try {
await this.projectService.renameConfigFileOnDisk(configId, newFileName);
if (this.projectService.updateConfigName(configId, newName, newFileName)) {
vscode.window.showInformationMessage(`✅ 配置/文件名已更新: ${newName} / ${newFileName}`);
await this.saveCurrentProjectData();
this.updateWebview();
}
}
catch (error) {
vscode.window.showErrorMessage(`❌ 重命名磁盘文件失败: ${error}`);
}
}
async createConfig(name) {
const fileName = this.projectService.sanitizeFileName(name);
const check = this.projectService.isConfigOrFolderConflict(this.currentContainerId, name, fileName);
if (check.nameConflict || check.fileConflict) {
vscode.window.showErrorMessage(`❌ 配置名称或文件名 "${name}" 已存在`);
return;
}
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;
}
const check = this.projectService.isConfigOrFolderConflict(this.currentContainerId, displayName, folderName);
if (check.nameConflict || check.fileConflict) {
vscode.window.showErrorMessage(`❌ 合并后的文件夹名称或显示名 "${displayName}" 已存在`);
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;
}
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');
}
}
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 || '未知容器';
}
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;
}
}
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 folderNames = GitService_1.GitService.generateModuleFolderName(url, branch);
const check = this.projectService.isConfigOrFolderConflict(this.currentContainerId, repoDisplayName, folderNames.folderName);
if (check.nameConflict || check.fileConflict) {
vscode.window.showErrorMessage(`❌ 克隆分支失败:名为 "${folderNames.folderName}" 的模块已存在`);
failCount++;
continue;
}
const progressPercent = (i / branches.length) * 100;
progress.report({
increment: progressPercent,
message: `克隆分支: ${branch} (${i + 1}/${branches.length})`
});
try {
await this.addGitModuleFolder(url, repoDisplayName, folderNames.folderName, branch, this.currentRepoForBranches?.username, this.currentRepoForBranches?.token);
successCount++;
}
catch (error) {
failCount++;
}
}
});
if (failCount === 0) {
vscode.window.showInformationMessage(`成功克隆 ${successCount} 个分支`);
}
else {
vscode.window.showWarningMessage(`克隆完成: ${successCount} 个成功, ${failCount} 个失败`);
}
}
catch (error) {
console.error('❌ 克隆分支失败:', error);
vscode.window.showErrorMessage(`克隆分支失败: ${error}`);
}
}
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);
try {
const projectId = await this.projectService.loadProjectData(targetPath);
if (projectId) {
this.currentProjectId = projectId;
successCount++;
}
else {
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();
}
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;
if (this.projectService.isAircraftNameExists(this.currentProjectId, aircraftName)) {
vscode.window.showErrorMessage(`❌ 克隆飞行器失败:名为 "${aircraftName}" 的飞行器已存在`);
failCount++;
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();
}
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;
if (this.projectService.isContainerNameExists(this.currentAircraftId, containerName)) {
vscode.window.showErrorMessage(`❌ 克隆容器失败:名为 "${containerName}" 的容器已存在`);
failCount++;
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;
}
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);
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 {
await GitService_1.GitService.cloneRepository(url, localPath, branch, (event) => {
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);
this.projectService.addModuleFolder(newFolder);
await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`Git 仓库克隆成功: ${displayName}`);
if (!this.isWebviewDisposed) {
this.currentModuleFolderId = folderId;
await this.loadModuleFolderFileTree(folderId);
}
}
catch (error) {
try {
if (require('fs').existsSync(localPath)) {
await require('fs').promises.rm(localPath, { recursive: true, force: true });
}
}
catch (cleanupError) { }
vscode.window.showErrorMessage(`克隆仓库失败: ${error}`);
throw error;
}
});
}
catch (error) {
vscode.window.showErrorMessage(`添加 Git 模块文件夹失败: ${error}`);
}
}
async renameModuleFolder(folderId, newName, newFolderName) {
const folder = this.projectService.getModuleFolder(folderId);
if (!folder) {
vscode.window.showErrorMessage('未找到模块文件夹');
return;
}
const check = this.projectService.isConfigOrFolderConflict(folder.containerId, newName, newFolderName, folderId);
if (check.nameConflict || check.fileConflict) {
vscode.window.showErrorMessage(`❌ 文件夹显示名称或磁盘名称 "${newFolderName}" 冲突`);
this.updateWebview();
return;
}
const fullPath = this.projectService.getModuleFolderFullPath(folder);
if (!fullPath) {
vscode.window.showErrorMessage('无法获取模块文件夹路径');
return;
}
const oldFolderName = folder.localPath.split('/').pop();
if (!oldFolderName)
return;
const newFullPath = path.join(path.dirname(fullPath), newFolderName);
try {
await this.projectService.renameDirectoryOnDisk(fullPath, newFullPath);
const newLocalPath = folder.localPath.replace(new RegExp(`/${oldFolderName}$`), '/' + newFolderName);
this.projectService.updateModuleFolder(folderId, {
name: newName,
localPath: newLocalPath
});
await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`✅ 已重命名文件夹: ${oldFolderName}${newFolderName} (显示名: ${newName})`);
this.updateWebview();
}
catch (error) {
vscode.window.showErrorMessage('重命名失败: ' + 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);
const configId = await this.projectService.createConfig(fileName, this.currentContainerId);
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}`);
}
}
async uploadProjectAircraftContainer(id, type, repo, branchName) {
let fullPath = null;
let name = '';
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('目录为空,无法上传');
}
progress.report({ increment: 5, message: '清理嵌套仓库...' });
await this.projectService.cleanNestedGitFolders(fullPath);
let isGitRepo = false;
if (fs.existsSync(path.join(fullPath, '.git'))) {
isGitRepo = true;
}
if (!isGitRepo) {
progress.report({ increment: 10, message: '初始化 Git 仓库...' });
await GitService_1.GitService.initRepository(fullPath, branchName);
}
progress.report({ increment: 20, message: '配置远程仓库...' });
try {
await GitService_1.GitService.removeRemote(fullPath);
}
catch (e) { }
await GitService_1.GitService.addRemote(fullPath, repo.url, repo.username, repo.token);
progress.report({ increment: 40, message: '提交并推送到远程仓库...' });
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) {
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) {
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) {
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) { }
}
});
}
async loadModuleFolderFileTree(folderId) {
if (this.isWebviewDisposed)
return;
const folder = this.projectService.getModuleFolder(folderId);
if (!folder)
return;
try {
this.panel.webview.postMessage({
type: 'moduleFolderLoading',
loading: true
});
}
catch (error) {
return;
}
try {
const fullPath = this.projectService.getModuleFolderFullPath(folder);
if (fullPath) {
const fileTree = await GitService_1.GitService.buildFileTree(fullPath);
this.currentModuleFolderFileTree = fileTree;
}
}
catch (error) {
this.currentModuleFolderFileTree = [];
}
if (this.isWebviewDisposed)
return;
try {
this.panel.webview.postMessage({
type: 'moduleFolderLoading',
loading: false
});
this.updateWebview();
}
catch (error) { }
}
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 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 loadProjectData(projectPath) {
try {
const projectId = await this.projectService.loadProjectData(projectPath);
if (projectId) {
this.currentProjectId = projectId;
this.currentView = 'aircrafts';
// [新增] 项目加载成功后,启动文件监听
this.setupFileWatcher(projectPath, projectId);
vscode.window.showInformationMessage(`项目数据已从 ${projectPath} 加载`);
this.updateWebview();
return true;
}
return false;
}
catch (error) {
vscode.window.showErrorMessage(`加载项目数据失败: ${error}`);
return false;
}
}
setupFileWatcher(projectPath, projectId) {
// 1. 如果已有监听器,先销毁
if (this.fileWatcher) {
this.fileWatcher.dispose();
}
// 2. 创建新的监听器:监听项目目录下的所有变化
// Pattern: /path/to/project/**/*
const pattern = new vscode.RelativePattern(projectPath, '**/*');
this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern, false, true, false); // 忽略 onChange只监听 Create/Delete
// 3. 定义处理逻辑(使用防抖,避免连续创建文件导致多次刷新)
const handleCreate = async (uri) => {
if (this.isWebviewDisposed)
return;
const fsPath = uri.fsPath;
const relativePath = path.relative(projectPath, fsPath);
const parts = relativePath.split(path.sep); // 使用系统分隔符
// 忽略 .git, .dcsp-data.json, 以及隐藏文件
if (parts.some(p => p.startsWith('.') || p === 'dcsp-data.json'))
return;
let dataChanged = false;
// 层级 1: 飞行器 (Folder)
if (parts.length === 1) {
// 检查是文件还是文件夹
const stat = await vscode.workspace.fs.stat(uri);
if ((stat.type & vscode.FileType.Directory) === vscode.FileType.Directory) {
await this.projectService.importAircraftFromExistingFolder(projectId, parts[0]);
dataChanged = true;
}
}
// 层级 2: 容器 (Folder)
else if (parts.length === 2) {
const stat = await vscode.workspace.fs.stat(uri);
if ((stat.type & vscode.FileType.Directory) === vscode.FileType.Directory) {
const aircraft = this.projectService.getAircraftsByProject(projectId).find(a => a.name === parts[0]);
if (aircraft) {
await this.projectService.importContainerFromExistingFolder(aircraft.id, parts[1]);
dataChanged = true;
}
}
}
// 层级 3: 配置 (File) 或 模块文件夹 (Folder)
else if (parts.length === 3) {
const [aircraftName, containerName, itemName] = parts;
const aircraft = this.projectService.getAircraftsByProject(projectId).find(a => a.name === aircraftName);
if (aircraft) {
const container = this.projectService.getContainersByAircraft(aircraft.id).find(c => c.name === containerName);
if (container) {
const stat = await vscode.workspace.fs.stat(uri);
if ((stat.type & vscode.FileType.File) === vscode.FileType.File) {
// 是文件 -> 注册为 Config
this.projectService.registerConfigFromDisk(itemName, itemName, container.id);
dataChanged = true;
}
else if ((stat.type & vscode.FileType.Directory) === vscode.FileType.Directory) {
// 是文件夹 -> 注册为 ModuleFolder
this.projectService.registerModuleFolderFromDisk(itemName, container.id, aircraft.id, projectId, aircraftName, containerName);
dataChanged = true;
}
}
}
}
if (dataChanged) {
await this.saveCurrentProjectData();
this.updateWebview();
}
};
const handleDelete = async (uri) => {
if (this.isWebviewDisposed)
return;
const changed = this.projectService.removeEntityByPath(uri.fsPath, projectId);
if (changed) {
await this.saveCurrentProjectData();
this.updateWebview();
}
};
// 4. 绑定事件 (防抖建议设为 500ms 左右)
const debouncedCreate = debounce(handleCreate, 500);
const debouncedDelete = debounce(handleDelete, 500);
this.fileWatcher.onDidCreate(uri => debouncedCreate(uri));
this.fileWatcher.onDidDelete(uri => debouncedDelete(uri));
}
async saveCurrentProjectData() {
if (!this.currentProjectId) {
return;
}
await this.projectService.saveCurrentProjectData(this.currentProjectId);
}
updateWebview() {
if (this.isWebviewDisposed)
return;
try {
this.panel.webview.html = this.getWebviewContent();
}
catch (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);
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