初步给项目、飞行器、容器页面添加了git拉取功能
This commit is contained in:
@@ -11,6 +11,12 @@
|
||||
"url": "http://117.72.162.127:3000/xb/build.git",
|
||||
"username": "xb",
|
||||
"token": "582e19830c58bdbfa8962f881ec873cce208ead0"
|
||||
},
|
||||
{
|
||||
"name": "项目模型",
|
||||
"url": "http://117.72.162.127:3000/xb/----.git",
|
||||
"username": "xb",
|
||||
"token": "582e19830c58bdbfa8962f881ec873cce208ead0"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -24,7 +24,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ConfigPanel = void 0;
|
||||
// src/panels/ConfigPanel.ts
|
||||
const vscode = __importStar(require("vscode"));
|
||||
const path = __importStar(require("path"));
|
||||
const ProjectView_1 = require("./views/ProjectView");
|
||||
@@ -60,6 +59,7 @@ class ConfigPanel {
|
||||
this.currentModuleFolderId = '';
|
||||
// 仓库配置
|
||||
this.repoConfigs = [];
|
||||
this.currentCloneScope = 'config';
|
||||
// 状态管理
|
||||
this.isWebviewDisposed = false;
|
||||
this.currentModuleFolderFileTree = [];
|
||||
@@ -93,9 +93,10 @@ class ConfigPanel {
|
||||
await this.loadRepoConfigs();
|
||||
}
|
||||
/**
|
||||
* 弹出仓库选择弹窗(仅用于“获取仓库 -> 获取分支”)
|
||||
* 按作用域弹出仓库选择弹窗(仅用于“获取仓库 -> 获取分支”)
|
||||
*/
|
||||
async openRepoSelect() {
|
||||
async openRepoSelectForScope(scope) {
|
||||
this.currentCloneScope = scope;
|
||||
await this.loadRepoConfigs();
|
||||
if (this.repoConfigs.length === 0) {
|
||||
vscode.window.showWarningMessage('尚未配置任何仓库,请先点击右上角 "仓库配置" 按钮编辑 dcsp-repos.json。');
|
||||
@@ -108,6 +109,12 @@ class ConfigPanel {
|
||||
repos: this.repoConfigs.map(r => ({ name: r.name }))
|
||||
});
|
||||
}
|
||||
/**
|
||||
* ConfigView 使用的默认版本(保持兼容)
|
||||
*/
|
||||
async openRepoSelect() {
|
||||
return this.openRepoSelectForScope('config');
|
||||
}
|
||||
/**
|
||||
* 弹出“上传代码”时的仓库 + 分支选择弹窗
|
||||
*/
|
||||
@@ -263,6 +270,9 @@ class ConfigPanel {
|
||||
// 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),
|
||||
@@ -587,7 +597,34 @@ class ConfigPanel {
|
||||
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;
|
||||
@@ -635,6 +672,206 @@ class ConfigPanel {
|
||||
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')) {
|
||||
@@ -1197,6 +1434,9 @@ class ConfigPanel {
|
||||
// =============================================
|
||||
// Webview 更新方法
|
||||
// =============================================
|
||||
// =============================================
|
||||
// Webview 更新方法
|
||||
// =============================================
|
||||
updateWebview() {
|
||||
if (this.isWebviewDisposed) {
|
||||
console.log('⚠️ Webview 已被销毁,跳过更新');
|
||||
@@ -1225,7 +1465,10 @@ class ConfigPanel {
|
||||
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);
|
||||
// ✅ 只在 UI 层隐藏名为 .git 的容器
|
||||
const projectContainers = this.projectService
|
||||
.getContainersByAircraft(this.currentAircraftId)
|
||||
.filter(c => c.name !== '.git');
|
||||
return this.containerView.render({
|
||||
project: project,
|
||||
aircraft: currentAircraft,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -27,6 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GitService = void 0;
|
||||
// src/panels/services/GitService.ts
|
||||
const fs = __importStar(require("fs"));
|
||||
const isomorphic_git_1 = __importDefault(require("isomorphic-git"));
|
||||
const node_1 = __importDefault(require("isomorphic-git/http/node"));
|
||||
@@ -255,16 +256,25 @@ class GitService {
|
||||
}
|
||||
/**
|
||||
* 构建文件树
|
||||
* 这里显式忽略:
|
||||
* - .git 目录
|
||||
* - .dcsp-data.json
|
||||
* - 其它以 . 开头的隐藏文件/目录
|
||||
*/
|
||||
static async buildFileTree(dir, relativePath = '') {
|
||||
try {
|
||||
const files = await fs.promises.readdir(dir);
|
||||
const tree = [];
|
||||
for (const file of files) {
|
||||
if (file.startsWith('.') && file !== '.git')
|
||||
// 1. 不要解析 .git
|
||||
if (file === '.git')
|
||||
continue;
|
||||
// 2. 不要解析项目数据文件
|
||||
if (file === '.dcsp-data.json')
|
||||
continue;
|
||||
// 3. 其它所有隐藏文件/目录统统忽略
|
||||
if (file.startsWith('.'))
|
||||
continue;
|
||||
const filePath = path.join(dir, file);
|
||||
const stats = await fs.promises.stat(filePath);
|
||||
const currentRelativePath = path.join(relativePath, file);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -58,8 +58,8 @@ class ProjectService {
|
||||
getProjectPath(projectId) {
|
||||
return this.projectPaths.get(projectId);
|
||||
}
|
||||
setProjectPath(projectId, path) {
|
||||
this.projectPaths.set(projectId, path);
|
||||
setProjectPath(projectId, pathStr) {
|
||||
this.projectPaths.set(projectId, pathStr);
|
||||
}
|
||||
async createProject(name) {
|
||||
const newId = this.generateUniqueId('p', this.projects);
|
||||
@@ -124,7 +124,7 @@ class ProjectService {
|
||||
const aircraft = this.aircrafts.find(a => a.id === aircraftId);
|
||||
if (!aircraft)
|
||||
return false;
|
||||
// ⚠️ 修正点:先在删除 containers 之前,算出要删的 containerIds
|
||||
// ⚠️ 先算出要删的 containerIds
|
||||
const relatedContainers = this.containers.filter(c => c.aircraftId === aircraftId);
|
||||
const containerIds = relatedContainers.map(c => c.id);
|
||||
// 删除飞机自身和容器
|
||||
@@ -135,7 +135,50 @@ class ProjectService {
|
||||
this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId));
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 从已存在的磁盘目录导入飞行器
|
||||
* 不会创建/删除任何文件,只在内存中补充 Aircraft / Container / ModuleFolder 数据
|
||||
*/
|
||||
async importAircraftFromExistingFolder(projectId, aircraftName) {
|
||||
// 已存在同名飞行器直接返回
|
||||
const existed = this.getAircraftsByProject(projectId).find(a => a.name === aircraftName);
|
||||
if (existed) {
|
||||
return existed.id;
|
||||
}
|
||||
const newId = this.generateUniqueId('a', this.aircrafts);
|
||||
const aircraft = {
|
||||
id: newId,
|
||||
name: aircraftName,
|
||||
projectId
|
||||
};
|
||||
this.aircrafts.push(aircraft);
|
||||
const projectPath = this.projectPaths.get(projectId);
|
||||
if (!projectPath) {
|
||||
return newId;
|
||||
}
|
||||
const aircraftDir = path.join(projectPath, aircraftName);
|
||||
if (!fs.existsSync(aircraftDir)) {
|
||||
// 目录不存在就不再解析子目录
|
||||
return newId;
|
||||
}
|
||||
// 每个子目录视为一个容器(排除 .git)
|
||||
const entries = await fs.promises.readdir(aircraftDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory())
|
||||
continue;
|
||||
if (entry.name === '.git')
|
||||
continue;
|
||||
await this.importContainerFromExistingFolder(newId, entry.name);
|
||||
}
|
||||
return newId;
|
||||
}
|
||||
// =============== 容器相关方法 ===============
|
||||
/**
|
||||
* ⚠️ 这里不再做 .git 过滤:
|
||||
* - 我们只会通过 createContainer / importContainerFromExistingFolder 创建容器
|
||||
* - importContainerFromExistingFolder 内部已经排除了 .git
|
||||
* 所以不会出现名字叫 ".git" 的容器
|
||||
*/
|
||||
getContainersByAircraft(aircraftId) {
|
||||
return this.containers.filter(c => c.aircraftId === aircraftId);
|
||||
}
|
||||
@@ -148,6 +191,7 @@ class ProjectService {
|
||||
};
|
||||
this.containers.push(newContainer);
|
||||
await this.createContainerDirectory(newContainer);
|
||||
// UI 手动创建的容器,仍然保留“默认两个配置”的行为
|
||||
await this.createDefaultConfigs(newContainer);
|
||||
return newId;
|
||||
}
|
||||
@@ -168,6 +212,73 @@ class ProjectService {
|
||||
this.moduleFolders = this.moduleFolders.filter(folder => folder.containerId !== containerId);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 从已存在的磁盘目录导入容器
|
||||
*
|
||||
* ✅ 你的最新需求:
|
||||
* - 不创建“默认两个配置”
|
||||
* - 自动扫描容器目录下的『子文件夹』
|
||||
* - 每个子文件夹创建一个 ModuleFolder(type: 'local')
|
||||
* - 排除 `.git` 子文件夹
|
||||
*/
|
||||
async importContainerFromExistingFolder(aircraftId, containerName) {
|
||||
if (containerName === '.git') {
|
||||
throw new Error('不能将 .git 导入为容器');
|
||||
}
|
||||
const existed = this.getContainersByAircraft(aircraftId).find(c => c.name === containerName);
|
||||
if (existed) {
|
||||
return existed.id;
|
||||
}
|
||||
const newId = this.generateUniqueId('c', this.containers);
|
||||
const container = {
|
||||
id: newId,
|
||||
name: containerName,
|
||||
aircraftId
|
||||
};
|
||||
this.containers.push(container);
|
||||
// 🚩 关键逻辑:扫描容器目录下的子文件夹 -> 创建 ModuleFolder
|
||||
await this.scanContainerModuleFolders(container);
|
||||
// 不再创建默认两个配置(不调用 createDefaultConfigs)
|
||||
// 也不创建目录(目录是 Git 克隆出来的,本来就存在)
|
||||
return newId;
|
||||
}
|
||||
/**
|
||||
* 扫描容器目录中的子文件夹(不含 .git),将其作为本地模块文件夹记录
|
||||
*/
|
||||
async scanContainerModuleFolders(container) {
|
||||
try {
|
||||
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
|
||||
if (!aircraft)
|
||||
return;
|
||||
const projectPath = this.projectPaths.get(aircraft.projectId);
|
||||
if (!projectPath)
|
||||
return;
|
||||
const containerDir = path.join(projectPath, aircraft.name, container.name);
|
||||
if (!fs.existsSync(containerDir)) {
|
||||
return;
|
||||
}
|
||||
const entries = await fs.promises.readdir(containerDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory())
|
||||
continue;
|
||||
if (entry.name === '.git')
|
||||
continue;
|
||||
const folderId = this.generateUniqueId('local-', this.moduleFolders);
|
||||
const relativePath = `/${aircraft.projectId}/${aircraft.name}/${container.name}/${entry.name}`;
|
||||
const moduleFolder = {
|
||||
id: folderId,
|
||||
name: entry.name,
|
||||
type: 'local',
|
||||
localPath: relativePath,
|
||||
containerId: container.id
|
||||
};
|
||||
this.moduleFolders.push(moduleFolder);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('扫描容器目录生成模块文件夹失败:', error);
|
||||
}
|
||||
}
|
||||
// =============== 配置相关方法 ===============
|
||||
getConfigsByContainer(containerId) {
|
||||
return this.configs.filter(cfg => cfg.containerId === containerId);
|
||||
@@ -295,6 +406,10 @@ class ProjectService {
|
||||
console.error(`确保容器目录存在失败: ${error}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 仅用于「新建容器」时的默认两个配置
|
||||
* (导入容器时不会调用)
|
||||
*/
|
||||
async createDefaultConfigs(container) {
|
||||
this.configs.push({
|
||||
id: this.generateUniqueId('cfg', this.configs),
|
||||
@@ -346,10 +461,11 @@ class ProjectService {
|
||||
this.containers = this.containers.filter(c => !aircraftIds.includes(c.aircraftId));
|
||||
this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId));
|
||||
this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId));
|
||||
// 加载新数据
|
||||
// ⭐ 载入新数据时顺便过滤掉名字为 ".git" 的容器(避免历史数据带进来)
|
||||
const cleanedContainers = data.containers.filter(c => c.name !== '.git');
|
||||
this.projects.push(...data.projects);
|
||||
this.aircrafts.push(...data.aircrafts);
|
||||
this.containers.push(...data.containers);
|
||||
this.containers.push(...cleanedContainers);
|
||||
this.configs.push(...data.configs);
|
||||
this.moduleFolders.push(...data.moduleFolders);
|
||||
this.projectPaths.set(projectId, projectPath);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,6 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AircraftView = void 0;
|
||||
// src/panels/views/AircraftView.ts
|
||||
const BaseView_1 = require("./BaseView");
|
||||
class AircraftView extends BaseView_1.BaseView {
|
||||
render(data) {
|
||||
@@ -26,6 +25,7 @@ class AircraftView extends BaseView_1.BaseView {
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>飞行器配置</title>
|
||||
${this.getStyles()}
|
||||
${this.getRepoSelectScript()}
|
||||
<style>
|
||||
.header {
|
||||
display: flex;
|
||||
@@ -59,7 +59,7 @@ class AircraftView extends BaseView_1.BaseView {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin: 0 auto;
|
||||
/* ❌ 原来这里有 margin: 0 auto; 已去掉,避免“获取仓库”按钮被居中 */
|
||||
}
|
||||
.btn-new:hover {
|
||||
background: var(--vscode-button-hoverBackground);
|
||||
@@ -77,6 +77,81 @@ class AircraftView extends BaseView_1.BaseView {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 仓库 + 分支树公共样式 */
|
||||
.config-section {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.section-title {
|
||||
margin: 30px 0 15px 0;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid var(--vscode-panel-border);
|
||||
color: var(--vscode-titleBar-activeForeground);
|
||||
}
|
||||
.url-input-section {
|
||||
background: var(--vscode-panel-background);
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
}
|
||||
.branch-selection {
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
}
|
||||
.branch-tree {
|
||||
font-family: 'Courier New', monospace;
|
||||
user-select: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.branch-node {
|
||||
padding: 2px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.branch-node:hover {
|
||||
background: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
.branch-icon {
|
||||
margin-right: 4px;
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
.branch-expand {
|
||||
margin-right: 2px;
|
||||
cursor: pointer;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
.branch-checkbox {
|
||||
margin-right: 6px;
|
||||
}
|
||||
.branch-name {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
}
|
||||
.branch-children {
|
||||
margin-left: 16px;
|
||||
border-left: 1px solid var(--vscode-panel-border);
|
||||
padding-left: 8px;
|
||||
}
|
||||
.branch-leaf {
|
||||
margin-left: 16px;
|
||||
}
|
||||
.current-branch {
|
||||
color: var(--vscode-gitDecoration-untrackedResourceForeground);
|
||||
font-weight: bold;
|
||||
}
|
||||
.tree-icon {
|
||||
font-size: 2em;
|
||||
}
|
||||
.branch-tree-title {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -103,6 +178,21 @@ class AircraftView extends BaseView_1.BaseView {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 飞行器云仓库 -->
|
||||
<div class="config-section">
|
||||
<h3 class="section-title">🛫 飞行器云仓库</h3>
|
||||
<div class="url-input-section">
|
||||
<h4>🔗 获取飞行器仓库</h4>
|
||||
<div style="display: flex; gap: 10px; margin-top: 10px; align-items: center;">
|
||||
<button class="btn-new" onclick="openRepoSelectForAircraft()">获取仓库</button>
|
||||
<span style="font-size: 12px; color: var(--vscode-descriptionForeground);">
|
||||
从仓库配置中选择 Git 仓库,选择分支后可将飞行器代码克隆到当前项目下(以分支名作为飞行器名称)
|
||||
</span>
|
||||
</div>
|
||||
<div id="branchSelectionContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const vscode = acquireVsCodeApi();
|
||||
|
||||
@@ -183,6 +273,164 @@ class AircraftView extends BaseView_1.BaseView {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ======== Git 仓库 & 分支树 - AircraftView 作用域 ========
|
||||
|
||||
let selectedBranches = new Set();
|
||||
let branchTreeData = [];
|
||||
|
||||
function openRepoSelectForAircraft() {
|
||||
vscode.postMessage({
|
||||
type: 'openRepoSelectForAircraft'
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBranch(branchName) {
|
||||
const checkbox = document.getElementById('branch-' + branchName.replace(/[^a-zA-Z0-9-]/g, '-'));
|
||||
if (checkbox && checkbox.checked) {
|
||||
selectedBranches.add(branchName);
|
||||
} else {
|
||||
selectedBranches.delete(branchName);
|
||||
}
|
||||
}
|
||||
|
||||
function cloneSelectedBranches() {
|
||||
if (selectedBranches.size === 0) {
|
||||
alert('请至少选择一个分支');
|
||||
return;
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: 'cloneBranches',
|
||||
branches: Array.from(selectedBranches)
|
||||
});
|
||||
|
||||
selectedBranches.clear();
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||
document.getElementById('branchSelectionContainer').innerHTML = '';
|
||||
}
|
||||
|
||||
function cancelBranchSelection() {
|
||||
selectedBranches.clear();
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||
document.getElementById('branchSelectionContainer').innerHTML = '';
|
||||
vscode.postMessage({
|
||||
type: 'cancelBranchSelection'
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBranchNode(nodeId) {
|
||||
const node = findNodeById(branchTreeData, nodeId);
|
||||
if (node && !node.isLeaf) {
|
||||
node.expanded = !node.expanded;
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
}
|
||||
|
||||
function expandAllBranches() {
|
||||
setAllExpanded(branchTreeData, true);
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
|
||||
function collapseAllBranches() {
|
||||
setAllExpanded(branchTreeData, false);
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
|
||||
function setAllExpanded(nodes, expanded) {
|
||||
nodes.forEach(node => {
|
||||
if (!node.isLeaf) {
|
||||
node.expanded = expanded;
|
||||
if (node.children) {
|
||||
setAllExpanded(node.children, expanded);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function findNodeById(nodes, nodeId) {
|
||||
for (const node of nodes) {
|
||||
if (node.fullName === nodeId) return node;
|
||||
if (node.children) {
|
||||
const found = findNodeById(node.children, nodeId);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderBranchTree(treeData) {
|
||||
const container = document.getElementById('branchSelectionContainer');
|
||||
|
||||
if (!treeData || treeData.length === 0) {
|
||||
container.innerHTML = '<div style="text-align: center; padding: 10px; color: var(--vscode-descriptionForeground);">未找到分支</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">';
|
||||
html += '<h4 class="branch-tree-title" style="margin: 0 0 8px 0;"><span class="tree-icon">🌳</span> 分支树</h4>';
|
||||
|
||||
html += '<div style="margin-bottom: 8px;">';
|
||||
html += '<button class="back-btn" onclick="expandAllBranches()" style="margin-right: 8px; padding: 2px 6px; font-size: 11px;">展开全部</button>';
|
||||
html += '<button class="back-btn" onclick="collapseAllBranches()" style="padding: 2px 6px; font-size: 11px;">收起全部</button>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="branch-tree">';
|
||||
html += renderBranchNodes(treeData, 0);
|
||||
html += '</div>';
|
||||
|
||||
html += '<div style="margin-top: 12px;">';
|
||||
html += '<button class="btn-new" onclick="cloneSelectedBranches()" style="margin-right: 8px; padding: 4px 8px; font-size: 12px;">✅ 克隆选中分支</button>';
|
||||
html += '<button class="back-btn" onclick="cancelBranchSelection()" style="padding: 4px 8px; font-size: 12px;">取消</button>';
|
||||
html += '</div></div>';
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderBranchNodes(nodes, level) {
|
||||
let html = '';
|
||||
nodes.forEach(node => {
|
||||
const indent = level * 20;
|
||||
const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
|
||||
if (node.isLeaf) {
|
||||
html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">';
|
||||
html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" ';
|
||||
html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\\'' + node.fullName + '\\')">';
|
||||
html += '<span class="branch-icon">🌿</span>';
|
||||
html += '<span class="branch-name ' + (node.branch && node.branch.isCurrent ? 'current-branch' : '') + '">';
|
||||
html += node.name;
|
||||
html += (node.branch && node.branch.isCurrent ? ' ⭐' : '');
|
||||
html += '</span>';
|
||||
html += '</div>';
|
||||
} else {
|
||||
html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">';
|
||||
html += '<span class="branch-expand" onclick="toggleBranchNode(\\'' + node.fullName + '\\')">';
|
||||
html += (node.expanded ? '🪵' : '🪵');
|
||||
html += '</span>';
|
||||
html += '<span class="branch-icon">🪵</span>';
|
||||
html += '<span class="branch-name">' + node.name + '</span>';
|
||||
html += '</div>';
|
||||
|
||||
if (node.expanded && node.children && node.children.length > 0) {
|
||||
html += '<div class="branch-children">';
|
||||
html += renderBranchNodes(node.children, level + 1);
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
window.addEventListener('message', event => {
|
||||
const message = event.data;
|
||||
if (message.type === 'branchesFetched') {
|
||||
branchTreeData = message.branchTree || [];
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"AircraftView.js","sourceRoot":"","sources":["../../../src/panels/views/AircraftView.ts"],"names":[],"mappings":";;;AAAA,mCAAmC;AACnC,yCAAsC;AAGtC,MAAa,YAAa,SAAQ,mBAAQ;IACtC,MAAM,CAAC,IAAwC;QAC3C,MAAM,SAAS,GAAG,IAAI,EAAE,SAAS,IAAI,EAAE,CAAC;QAExC,MAAM,aAAa,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;;;oEAGY,QAAQ,CAAC,EAAE,QAAQ,QAAQ,CAAC,IAAI;;;2EAGzB,QAAQ,CAAC,EAAE,OAAO,QAAQ,CAAC,SAAS;;;0EAGrC,QAAQ,CAAC,EAAE;;;SAG5E,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEZ,OAAO;;;;;;MAMT,IAAI,CAAC,SAAS,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAqER,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA2FnB,CAAC;IACL,CAAC;CACJ;AA1LD,oCA0LC"}
|
||||
{"version":3,"file":"AircraftView.js","sourceRoot":"","sources":["../../../src/panels/views/AircraftView.ts"],"names":[],"mappings":";;;AAAA,yCAAsC;AAGtC,MAAa,YAAa,SAAQ,mBAAQ;IACtC,MAAM,CAAC,IAAwC;QAC3C,MAAM,SAAS,GAAG,IAAI,EAAE,SAAS,IAAI,EAAE,CAAC;QAExC,MAAM,aAAa,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;;;oEAGY,QAAQ,CAAC,EAAE,QAAQ,QAAQ,CAAC,IAAI;;;2EAGzB,QAAQ,CAAC,EAAE,OAAO,QAAQ,CAAC,SAAS;;;0EAGrC,QAAQ,CAAC,EAAE;;;SAG5E,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEZ,OAAO;;;;;;MAMT,IAAI,CAAC,SAAS,EAAE;MAChB,IAAI,CAAC,mBAAmB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAgJlB,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAwQnB,CAAC;IACL,CAAC;CACJ;AAnbD,oCAmbC"}
|
||||
@@ -1,7 +1,6 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ContainerView = void 0;
|
||||
// src/panels/views/ContainerView.ts
|
||||
const BaseView_1 = require("./BaseView");
|
||||
class ContainerView extends BaseView_1.BaseView {
|
||||
render(data) {
|
||||
@@ -28,6 +27,117 @@ class ContainerView extends BaseView_1.BaseView {
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>容器管理</title>
|
||||
${this.getStyles()}
|
||||
${this.getRepoSelectScript()}
|
||||
<style>
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.back-btn {
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.back-btn:hover {
|
||||
background: var(--vscode-button-secondaryHoverBackground);
|
||||
}
|
||||
.btn-new {
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
/* ❌ 原来这里有 margin: 0 auto; 已去掉,避免“获取仓库”按钮被居中 */
|
||||
}
|
||||
.btn-new:hover {
|
||||
background: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
|
||||
/* 仓库 + 分支树公共样式 */
|
||||
.config-section {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.section-title {
|
||||
margin: 30px 0 15px 0;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid var(--vscode-panel-border);
|
||||
color: var(--vscode-titleBar-activeForeground);
|
||||
}
|
||||
.url-input-section {
|
||||
background: var(--vscode-panel-background);
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
}
|
||||
.branch-selection {
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
}
|
||||
.branch-tree {
|
||||
font-family: 'Courier New', monospace;
|
||||
user-select: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.branch-node {
|
||||
padding: 2px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.branch-node:hover {
|
||||
background: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
.branch-icon {
|
||||
margin-right: 4px;
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
.branch-expand {
|
||||
margin-right: 2px;
|
||||
cursor: pointer;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
.branch-checkbox {
|
||||
margin-right: 6px;
|
||||
}
|
||||
.branch-name {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
}
|
||||
.branch-children {
|
||||
margin-left: 16px;
|
||||
border-left: 1px solid var(--vscode-panel-border);
|
||||
padding-left: 8px;
|
||||
}
|
||||
.branch-leaf {
|
||||
margin-left: 16px;
|
||||
}
|
||||
.current-branch {
|
||||
color: var(--vscode-gitDecoration-untrackedResourceForeground);
|
||||
font-weight: bold;
|
||||
}
|
||||
.tree-icon {
|
||||
font-size: 2em;
|
||||
}
|
||||
.branch-tree-title {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
@@ -53,6 +163,21 @@ class ContainerView extends BaseView_1.BaseView {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 容器云仓库 -->
|
||||
<div class="config-section">
|
||||
<h3 class="section-title">📦 容器云仓库</h3>
|
||||
<div class="url-input-section">
|
||||
<h4>🔗 获取容器仓库</h4>
|
||||
<div style="display: flex; gap: 10px; margin-top: 10px; align-items: center;">
|
||||
<button class="btn-new" onclick="openRepoSelectForContainer()">获取仓库</button>
|
||||
<span style="font-size: 12px; color: var(--vscode-descriptionForeground);">
|
||||
从仓库配置中选择 Git 仓库,选择分支后可将容器代码克隆到当前飞行器下(以分支名作为容器名称)
|
||||
</span>
|
||||
</div>
|
||||
<div id="branchSelectionContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const vscode = acquireVsCodeApi();
|
||||
|
||||
@@ -115,6 +240,164 @@ class ContainerView extends BaseView_1.BaseView {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ======== Git 仓库 & 分支树 - ContainerView 作用域 ========
|
||||
|
||||
let selectedBranches = new Set();
|
||||
let branchTreeData = [];
|
||||
|
||||
function openRepoSelectForContainer() {
|
||||
vscode.postMessage({
|
||||
type: 'openRepoSelectForContainer'
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBranch(branchName) {
|
||||
const checkbox = document.getElementById('branch-' + branchName.replace(/[^a-zA-Z0-9-]/g, '-'));
|
||||
if (checkbox && checkbox.checked) {
|
||||
selectedBranches.add(branchName);
|
||||
} else {
|
||||
selectedBranches.delete(branchName);
|
||||
}
|
||||
}
|
||||
|
||||
function cloneSelectedBranches() {
|
||||
if (selectedBranches.size === 0) {
|
||||
alert('请至少选择一个分支');
|
||||
return;
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: 'cloneBranches',
|
||||
branches: Array.from(selectedBranches)
|
||||
});
|
||||
|
||||
selectedBranches.clear();
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||
document.getElementById('branchSelectionContainer').innerHTML = '';
|
||||
}
|
||||
|
||||
function cancelBranchSelection() {
|
||||
selectedBranches.clear();
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||
document.getElementById('branchSelectionContainer').innerHTML = '';
|
||||
vscode.postMessage({
|
||||
type: 'cancelBranchSelection'
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBranchNode(nodeId) {
|
||||
const node = findNodeById(branchTreeData, nodeId);
|
||||
if (node && !node.isLeaf) {
|
||||
node.expanded = !node.expanded;
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
}
|
||||
|
||||
function expandAllBranches() {
|
||||
setAllExpanded(branchTreeData, true);
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
|
||||
function collapseAllBranches() {
|
||||
setAllExpanded(branchTreeData, false);
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
|
||||
function setAllExpanded(nodes, expanded) {
|
||||
nodes.forEach(node => {
|
||||
if (!node.isLeaf) {
|
||||
node.expanded = expanded;
|
||||
if (node.children) {
|
||||
setAllExpanded(node.children, expanded);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function findNodeById(nodes, nodeId) {
|
||||
for (const node of nodes) {
|
||||
if (node.fullName === nodeId) return node;
|
||||
if (node.children) {
|
||||
const found = findNodeById(node.children, nodeId);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderBranchTree(treeData) {
|
||||
const container = document.getElementById('branchSelectionContainer');
|
||||
|
||||
if (!treeData || treeData.length === 0) {
|
||||
container.innerHTML = '<div style="text-align: center; padding: 10px; color: var(--vscode-descriptionForeground);">未找到分支</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">';
|
||||
html += '<h4 class="branch-tree-title" style="margin: 0 0 8px 0;"><span class="tree-icon">🌳</span> 分支树</h4>';
|
||||
|
||||
html += '<div style="margin-bottom: 8px;">';
|
||||
html += '<button class="back-btn" onclick="expandAllBranches()" style="margin-right: 8px; padding: 2px 6px; font-size: 11px;">展开全部</button>';
|
||||
html += '<button class="back-btn" onclick="collapseAllBranches()" style="padding: 2px 6px; font-size: 11px;">收起全部</button>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="branch-tree">';
|
||||
html += renderBranchNodes(treeData, 0);
|
||||
html += '</div>';
|
||||
|
||||
html += '<div style="margin-top: 12px;">';
|
||||
html += '<button class="btn-new" onclick="cloneSelectedBranches()" style="margin-right: 8px; padding: 4px 8px; font-size: 12px;">✅ 克隆选中分支</button>';
|
||||
html += '<button class="back-btn" onclick="cancelBranchSelection()" style="padding: 4px 8px; font-size: 12px;">取消</button>';
|
||||
html += '</div></div>';
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderBranchNodes(nodes, level) {
|
||||
let html = '';
|
||||
nodes.forEach(node => {
|
||||
const indent = level * 20;
|
||||
const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
|
||||
if (node.isLeaf) {
|
||||
html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">';
|
||||
html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" ';
|
||||
html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\\'' + node.fullName + '\\')">';
|
||||
html += '<span class="branch-icon">🌿</span>';
|
||||
html += '<span class="branch-name ' + (node.branch && node.branch.isCurrent ? 'current-branch' : '') + '">';
|
||||
html += node.name;
|
||||
html += (node.branch && node.branch.isCurrent ? ' ⭐' : '');
|
||||
html += '</span>';
|
||||
html += '</div>';
|
||||
} else {
|
||||
html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">';
|
||||
html += '<span class="branch-expand" onclick="toggleBranchNode(\\'' + node.fullName + '\\')">';
|
||||
html += (node.expanded ? '🪵' : '🪵');
|
||||
html += '</span>';
|
||||
html += '<span class="branch-icon">🪵</span>';
|
||||
html += '<span class="branch-name">' + node.name + '</span>';
|
||||
html += '</div>';
|
||||
|
||||
if (node.expanded && node.children && node.children.length > 0) {
|
||||
html += '<div class="branch-children">';
|
||||
html += renderBranchNodes(node.children, level + 1);
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
window.addEventListener('message', event => {
|
||||
const message = event.data;
|
||||
if (message.type === 'branchesFetched') {
|
||||
branchTreeData = message.branchTree || [];
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"ContainerView.js","sourceRoot":"","sources":["../../../src/panels/views/ContainerView.ts"],"names":[],"mappings":";;;AAAA,oCAAoC;AACpC,yCAAsC;AAGtC,MAAa,aAAc,SAAQ,mBAAQ;IACvC,MAAM,CAAC,IAAyB;QAC5B,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC;QAC9B,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,CAAC;QAChC,MAAM,UAAU,GAAG,IAAI,EAAE,UAAU,IAAI,EAAE,CAAC;QAE1C,MAAM,cAAc,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,SAA4B,EAAE,EAAE,CAAC;;;yEAGP,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC,IAAI,UAAU,SAAS,CAAC,IAAI;;;4EAGtD,SAAS,CAAC,EAAE;;;2EAGb,SAAS,CAAC,EAAE;;;SAG9E,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEZ,OAAO;;;;;;MAMT,IAAI,CAAC,SAAS,EAAE;;;;gFAI0D,QAAQ,EAAE,IAAI,IAAI,OAAO;;;;;;;;;;;;;cAa3F,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAyEpB,CAAC;IACL,CAAC;CACJ;AAtHD,sCAsHC"}
|
||||
{"version":3,"file":"ContainerView.js","sourceRoot":"","sources":["../../../src/panels/views/ContainerView.ts"],"names":[],"mappings":";;;AAAA,yCAAsC;AAGtC,MAAa,aAAc,SAAQ,mBAAQ;IACvC,MAAM,CAAC,IAAyB;QAC5B,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC;QAC9B,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,CAAC;QAChC,MAAM,UAAU,GAAG,IAAI,EAAE,UAAU,IAAI,EAAE,CAAC;QAE1C,MAAM,cAAc,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,SAA4B,EAAE,EAAE,CAAC;;;yEAGP,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC,IAAI,UAAU,SAAS,CAAC,IAAI;;;4EAGtD,SAAS,CAAC,EAAE;;;2EAGb,SAAS,CAAC,EAAE;;;SAG9E,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEZ,OAAO;;;;;;MAMT,IAAI,CAAC,SAAS,EAAE;MAChB,IAAI,CAAC,mBAAmB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gFAkHgD,QAAQ,EAAE,IAAI,IAAI,OAAO;;;;;;;;;;;;;cAa3F,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAsPpB,CAAC;IACL,CAAC;CACJ;AAlZD,sCAkZC"}
|
||||
@@ -1,7 +1,6 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ProjectView = void 0;
|
||||
// src/panels/views/ProjectView.ts
|
||||
const BaseView_1 = require("./BaseView");
|
||||
class ProjectView extends BaseView_1.BaseView {
|
||||
render(data) {
|
||||
@@ -37,6 +36,7 @@ class ProjectView extends BaseView_1.BaseView {
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>数字卫星构建平台</title>
|
||||
${this.getStyles()}
|
||||
${this.getRepoSelectScript()}
|
||||
<style>
|
||||
.satellite-icon {
|
||||
font-size: 2.5em;
|
||||
@@ -83,15 +83,102 @@ class ProjectView extends BaseView_1.BaseView {
|
||||
.btn-action:hover {
|
||||
background: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
/* 统一“主要操作按钮”的样式 */
|
||||
.btn-new {
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.btn-new:hover {
|
||||
background: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
.btn-open {
|
||||
background: var(--vscode-input-background);
|
||||
color: var(--vscode-input-foreground);
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
}
|
||||
|
||||
/* 仓库 & 分支树公共样式 */
|
||||
.config-section {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.section-title {
|
||||
margin: 30px 0 15px 0;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid var(--vscode-panel-border);
|
||||
color: var(--vscode-titleBar-activeForeground);
|
||||
}
|
||||
.url-input-section {
|
||||
background: var(--vscode-panel-background);
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
}
|
||||
.branch-selection {
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
}
|
||||
.branch-tree {
|
||||
font-family: 'Courier New', monospace;
|
||||
user-select: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.branch-node {
|
||||
padding: 2px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.branch-node:hover {
|
||||
background: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
.branch-icon {
|
||||
margin-right: 4px;
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
.branch-expand {
|
||||
margin-right: 2px;
|
||||
cursor: pointer;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
.branch-checkbox {
|
||||
margin-right: 6px;
|
||||
}
|
||||
.branch-name {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
}
|
||||
.branch-children {
|
||||
margin-left: 16px;
|
||||
border-left: 1px solid var(--vscode-panel-border);
|
||||
padding-left: 8px;
|
||||
}
|
||||
.branch-leaf {
|
||||
margin-left: 16px;
|
||||
}
|
||||
.current-branch {
|
||||
color: var(--vscode-gitDecoration-untrackedResourceForeground);
|
||||
font-weight: bold;
|
||||
}
|
||||
.tree-icon {
|
||||
font-size: 2em;
|
||||
}
|
||||
.branch-tree-title {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -125,6 +212,21 @@ class ProjectView extends BaseView_1.BaseView {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 项目云仓库 -->
|
||||
<div class="config-section">
|
||||
<h3 class="section-title">📚 项目云仓库</h3>
|
||||
<div class="url-input-section">
|
||||
<h4>🔗 获取项目仓库</h4>
|
||||
<div style="display: flex; gap: 10px; margin-top: 10px; align-items: center;">
|
||||
<button class="btn-new" onclick="openRepoSelectForProject()">获取仓库</button>
|
||||
<span style="font-size: 12px; color: var(--vscode-descriptionForeground);">
|
||||
从仓库配置中选择 Git 仓库,选择分支后可将完整项目克隆到本地(包含 dscp-data.json)
|
||||
</span>
|
||||
</div>
|
||||
<div id="branchSelectionContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const vscode = acquireVsCodeApi();
|
||||
|
||||
@@ -218,6 +320,164 @@ class ProjectView extends BaseView_1.BaseView {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ======== Git 仓库 & 分支树 - ProjectView 作用域 ========
|
||||
|
||||
let selectedBranches = new Set();
|
||||
let branchTreeData = [];
|
||||
|
||||
function openRepoSelectForProject() {
|
||||
vscode.postMessage({
|
||||
type: 'openRepoSelectForProject'
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBranch(branchName) {
|
||||
const checkbox = document.getElementById('branch-' + branchName.replace(/[^a-zA-Z0-9-]/g, '-'));
|
||||
if (checkbox && checkbox.checked) {
|
||||
selectedBranches.add(branchName);
|
||||
} else {
|
||||
selectedBranches.delete(branchName);
|
||||
}
|
||||
}
|
||||
|
||||
function cloneSelectedBranches() {
|
||||
if (selectedBranches.size === 0) {
|
||||
alert('请至少选择一个分支');
|
||||
return;
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: 'cloneBranches',
|
||||
branches: Array.from(selectedBranches)
|
||||
});
|
||||
|
||||
selectedBranches.clear();
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||
document.getElementById('branchSelectionContainer').innerHTML = '';
|
||||
}
|
||||
|
||||
function cancelBranchSelection() {
|
||||
selectedBranches.clear();
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||
document.getElementById('branchSelectionContainer').innerHTML = '';
|
||||
vscode.postMessage({
|
||||
type: 'cancelBranchSelection'
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBranchNode(nodeId) {
|
||||
const node = findNodeById(branchTreeData, nodeId);
|
||||
if (node && !node.isLeaf) {
|
||||
node.expanded = !node.expanded;
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
}
|
||||
|
||||
function expandAllBranches() {
|
||||
setAllExpanded(branchTreeData, true);
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
|
||||
function collapseAllBranches() {
|
||||
setAllExpanded(branchTreeData, false);
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
|
||||
function setAllExpanded(nodes, expanded) {
|
||||
nodes.forEach(node => {
|
||||
if (!node.isLeaf) {
|
||||
node.expanded = expanded;
|
||||
if (node.children) {
|
||||
setAllExpanded(node.children, expanded);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function findNodeById(nodes, nodeId) {
|
||||
for (const node of nodes) {
|
||||
if (node.fullName === nodeId) return node;
|
||||
if (node.children) {
|
||||
const found = findNodeById(node.children, nodeId);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderBranchTree(treeData) {
|
||||
const container = document.getElementById('branchSelectionContainer');
|
||||
|
||||
if (!treeData || treeData.length === 0) {
|
||||
container.innerHTML = '<div style="text-align: center; padding: 10px; color: var(--vscode-descriptionForeground);">未找到分支</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">';
|
||||
html += '<h4 class="branch-tree-title" style="margin: 0 0 8px 0;"><span class="tree-icon">🌳</span> 分支树</h4>';
|
||||
|
||||
html += '<div style="margin-bottom: 8px;">';
|
||||
html += '<button class="back-btn" onclick="expandAllBranches()" style="margin-right: 8px; padding: 2px 6px; font-size: 11px;">展开全部</button>';
|
||||
html += '<button class="back-btn" onclick="collapseAllBranches()" style="padding: 2px 6px; font-size: 11px;">收起全部</button>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="branch-tree">';
|
||||
html += renderBranchNodes(treeData, 0);
|
||||
html += '</div>';
|
||||
|
||||
html += '<div style="margin-top: 12px;">';
|
||||
html += '<button class="btn-new" onclick="cloneSelectedBranches()" style="margin-right: 8px; padding: 4px 8px; font-size: 12px;">✅ 克隆选中分支</button>';
|
||||
html += '<button class="back-btn" onclick="cancelBranchSelection()" style="padding: 4px 8px; font-size: 12px;">取消</button>';
|
||||
html += '</div></div>';
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderBranchNodes(nodes, level) {
|
||||
let html = '';
|
||||
nodes.forEach(node => {
|
||||
const indent = level * 20;
|
||||
const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
|
||||
if (node.isLeaf) {
|
||||
html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">';
|
||||
html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" ';
|
||||
html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\\'' + node.fullName + '\\')">';
|
||||
html += '<span class="branch-icon">🌿</span>';
|
||||
html += '<span class="branch-name ' + (node.branch && node.branch.isCurrent ? 'current-branch' : '') + '">';
|
||||
html += node.name;
|
||||
html += (node.branch && node.branch.isCurrent ? ' ⭐' : '');
|
||||
html += '</span>';
|
||||
html += '</div>';
|
||||
} else {
|
||||
html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">';
|
||||
html += '<span class="branch-expand" onclick="toggleBranchNode(\\'' + node.fullName + '\\')">';
|
||||
html += (node.expanded ? '🪵' : '🪵');
|
||||
html += '</span>';
|
||||
html += '<span class="branch-icon">🪵</span>';
|
||||
html += '<span class="branch-name">' + node.name + '</span>';
|
||||
html += '</div>';
|
||||
|
||||
if (node.expanded && node.children && node.children.length > 0) {
|
||||
html += '<div class="branch-children">';
|
||||
html += renderBranchNodes(node.children, level + 1);
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
window.addEventListener('message', event => {
|
||||
const message = event.data;
|
||||
if (message.type === 'branchesFetched') {
|
||||
branchTreeData = message.branchTree || [];
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"ProjectView.js","sourceRoot":"","sources":["../../../src/panels/views/ProjectView.ts"],"names":[],"mappings":";;;AAAA,kCAAkC;AAClC,yCAAsC;AAGtC,MAAa,WAAY,SAAQ,mBAAQ;IACrC,MAAM,CAAC,IAA0E;QAC7E,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;QACtC,MAAM,YAAY,GAAG,IAAI,EAAE,YAAY,IAAI,IAAI,GAAG,EAAE,CAAC;QAErD,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAwB,EAAE,EAAE;YAC3D,MAAM,YAAY,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAClD,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YAC7C,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;YAEhD,OAAO;;;kEAG+C,OAAO,CAAC,EAAE,KAAK,UAAU,IAAI,OAAO,CAAC,IAAI;;0BAEjF,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,MAAM,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;;;;yEAItB,OAAO,CAAC,EAAE,OAAO,OAAO,CAAC,IAAI,MAAM,YAAY;0BAC9F,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;;;;yEAIqB,OAAO,CAAC,EAAE;;;SAG1E,CAAA;QAAA,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEb,OAAO;;;;;;MAMT,IAAI,CAAC,SAAS,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAyER,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA+GlB,CAAC;IACL,CAAC;CACJ;AA7ND,kCA6NC"}
|
||||
{"version":3,"file":"ProjectView.js","sourceRoot":"","sources":["../../../src/panels/views/ProjectView.ts"],"names":[],"mappings":";;;AAAA,yCAAsC;AAGtC,MAAa,WAAY,SAAQ,mBAAQ;IACrC,MAAM,CAAC,IAA0E;QAC7E,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;QACtC,MAAM,YAAY,GAAG,IAAI,EAAE,YAAY,IAAI,IAAI,GAAG,EAAE,CAAC;QAErD,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAwB,EAAE,EAAE;YAC3D,MAAM,YAAY,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAClD,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YAC7C,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;YAEhD,OAAO;;;kEAG+C,OAAO,CAAC,EAAE,KAAK,UAAU,IAAI,OAAO,CAAC,IAAI;;0BAEjF,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,MAAM,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;;;;yEAItB,OAAO,CAAC,EAAE,OAAO,OAAO,CAAC,IAAI,MAAM,YAAY;0BAC9F,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;;;;yEAIqB,OAAO,CAAC,EAAE;;;SAG1E,CAAA;QAAA,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEb,OAAO;;;;;;MAMT,IAAI,CAAC,SAAS,EAAE;MAChB,IAAI,CAAC,mBAAmB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAgKlB,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA4RlB,CAAC;IACL,CAAC;CACJ;AAleD,kCAkeC"}
|
||||
@@ -1,4 +1,3 @@
|
||||
// src/panels/ConfigPanel.ts
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { ProjectView } from './views/ProjectView';
|
||||
@@ -26,6 +25,8 @@ interface GitFileTree {
|
||||
children?: GitFileTree[];
|
||||
}
|
||||
|
||||
type CloneScope = 'project' | 'aircraft' | 'container' | 'config';
|
||||
|
||||
export class ConfigPanel {
|
||||
public static currentPanel: ConfigPanel | undefined;
|
||||
public readonly panel: vscode.WebviewPanel;
|
||||
@@ -44,6 +45,7 @@ export class ConfigPanel {
|
||||
// 仓库配置
|
||||
private repoConfigs: RepoConfigItem[] = [];
|
||||
private currentRepoForBranches: RepoConfigItem | undefined;
|
||||
private currentCloneScope: CloneScope = 'config';
|
||||
|
||||
// 状态管理
|
||||
private isWebviewDisposed: boolean = false;
|
||||
@@ -121,9 +123,10 @@ export class ConfigPanel {
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出仓库选择弹窗(仅用于“获取仓库 -> 获取分支”)
|
||||
* 按作用域弹出仓库选择弹窗(仅用于“获取仓库 -> 获取分支”)
|
||||
*/
|
||||
private async openRepoSelect(): Promise<void> {
|
||||
private async openRepoSelectForScope(scope: CloneScope): Promise<void> {
|
||||
this.currentCloneScope = scope;
|
||||
await this.loadRepoConfigs();
|
||||
|
||||
if (this.repoConfigs.length === 0) {
|
||||
@@ -139,6 +142,13 @@ export class ConfigPanel {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ConfigView 使用的默认版本(保持兼容)
|
||||
*/
|
||||
private async openRepoSelect(): Promise<void> {
|
||||
return this.openRepoSelectForScope('config');
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出“上传代码”时的仓库 + 分支选择弹窗
|
||||
*/
|
||||
@@ -338,7 +348,10 @@ export class ConfigPanel {
|
||||
|
||||
// Git 仓库管理
|
||||
'openRepoConfig': () => this.openRepoConfig(),
|
||||
'openRepoSelect': () => this.openRepoSelect(),
|
||||
'openRepoSelect': () => this.openRepoSelect(), // ConfigView 用
|
||||
'openRepoSelectForProject': () => this.openRepoSelectForScope('project'),
|
||||
'openRepoSelectForAircraft': () => this.openRepoSelectForScope('aircraft'),
|
||||
'openRepoSelectForContainer': () => this.openRepoSelectForScope('container'),
|
||||
'repoSelectedForBranches': (data) => this.handleRepoSelectedForBranches(data.repoName),
|
||||
|
||||
// Git 分支管理
|
||||
@@ -740,12 +753,41 @@ export class ConfigPanel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前作用域,把选中的分支克隆到不同层级
|
||||
*/
|
||||
private async cloneBranches(branches: string[]): Promise<void> {
|
||||
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 模块文件夹
|
||||
*/
|
||||
private async cloneBranchesToModuleFolders(branches: string[]): Promise<void> {
|
||||
if (!this.currentRepoForBranches) {
|
||||
vscode.window.showErrorMessage('请先通过"获取仓库"选择一个仓库');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = this.currentRepoForBranches.url;
|
||||
const repoDisplayName = this.currentRepoForBranches.name;
|
||||
|
||||
@@ -801,6 +843,257 @@ export class ConfigPanel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ProjectView 用:把分支克隆为完整项目
|
||||
* 每个分支会弹出一个路径输入框,克隆整个仓库到该路径,如果有 .dcsp-data.json 则自动加载为项目
|
||||
*/
|
||||
private async cloneBranchesToProjects(branches: string[]): Promise<void> {
|
||||
if (!this.currentRepoForBranches) {
|
||||
vscode.window.showErrorMessage('请先通过"获取仓库"选择一个仓库');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = this.currentRepoForBranches.url;
|
||||
|
||||
const tasks: { branch: string; targetPath: string }[] = [];
|
||||
|
||||
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.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
|
||||
*/
|
||||
private async cloneBranchesToAircrafts(branches: string[]): Promise<void> {
|
||||
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.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
|
||||
*/
|
||||
private async cloneBranchesToContainers(branches: string[]): Promise<void> {
|
||||
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.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();
|
||||
}
|
||||
|
||||
private async addGitModuleFolder(
|
||||
url: string,
|
||||
displayName: string,
|
||||
@@ -1484,6 +1777,10 @@ export class ConfigPanel {
|
||||
|
||||
// =============================================
|
||||
// Webview 更新方法
|
||||
// =============================================
|
||||
|
||||
// =============================================
|
||||
// Webview 更新方法
|
||||
// =============================================
|
||||
|
||||
private updateWebview() {
|
||||
@@ -1515,7 +1812,11 @@ export class ConfigPanel {
|
||||
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);
|
||||
|
||||
// ✅ 只在 UI 层隐藏名为 .git 的容器
|
||||
const projectContainers = this.projectService
|
||||
.getContainersByAircraft(this.currentAircraftId)
|
||||
.filter(c => c.name !== '.git');
|
||||
|
||||
return this.containerView.render({
|
||||
project: project,
|
||||
|
||||
13
src/panels/services/GitService.ts
Normal file → Executable file
13
src/panels/services/GitService.ts
Normal file → Executable file
@@ -1,3 +1,4 @@
|
||||
// src/panels/services/GitService.ts
|
||||
import * as fs from 'fs';
|
||||
import git from 'isomorphic-git';
|
||||
import http from 'isomorphic-git/http/node';
|
||||
@@ -290,6 +291,10 @@ export class GitService {
|
||||
|
||||
/**
|
||||
* 构建文件树
|
||||
* 这里显式忽略:
|
||||
* - .git 目录
|
||||
* - .dcsp-data.json
|
||||
* - 其它以 . 开头的隐藏文件/目录
|
||||
*/
|
||||
static async buildFileTree(dir: string, relativePath: string = ''): Promise<GitFileTree[]> {
|
||||
try {
|
||||
@@ -297,9 +302,15 @@ export class GitService {
|
||||
const tree: GitFileTree[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
if (file.startsWith('.') && file !== '.git') continue;
|
||||
// 1. 不要解析 .git
|
||||
if (file === '.git') continue;
|
||||
|
||||
// 2. 不要解析项目数据文件
|
||||
if (file === '.dcsp-data.json') continue;
|
||||
|
||||
// 3. 其它所有隐藏文件/目录统统忽略
|
||||
if (file.startsWith('.')) continue;
|
||||
|
||||
const filePath = path.join(dir, file);
|
||||
const stats = await fs.promises.stat(filePath);
|
||||
const currentRelativePath = path.join(relativePath, file);
|
||||
|
||||
@@ -44,8 +44,8 @@ export class ProjectService {
|
||||
return this.projectPaths.get(projectId);
|
||||
}
|
||||
|
||||
setProjectPath(projectId: string, path: string): void {
|
||||
this.projectPaths.set(projectId, path);
|
||||
setProjectPath(projectId: string, pathStr: string): void {
|
||||
this.projectPaths.set(projectId, pathStr);
|
||||
}
|
||||
|
||||
async createProject(name: string): Promise<string> {
|
||||
@@ -123,7 +123,7 @@ export class ProjectService {
|
||||
const aircraft = this.aircrafts.find(a => a.id === aircraftId);
|
||||
if (!aircraft) return false;
|
||||
|
||||
// ⚠️ 修正点:先在删除 containers 之前,算出要删的 containerIds
|
||||
// ⚠️ 先算出要删的 containerIds
|
||||
const relatedContainers = this.containers.filter(c => c.aircraftId === aircraftId);
|
||||
const containerIds = relatedContainers.map(c => c.id);
|
||||
|
||||
@@ -138,8 +138,56 @@ export class ProjectService {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从已存在的磁盘目录导入飞行器
|
||||
* 不会创建/删除任何文件,只在内存中补充 Aircraft / Container / ModuleFolder 数据
|
||||
*/
|
||||
async importAircraftFromExistingFolder(projectId: string, aircraftName: string): Promise<string> {
|
||||
// 已存在同名飞行器直接返回
|
||||
const existed = this.getAircraftsByProject(projectId).find(a => a.name === aircraftName);
|
||||
if (existed) {
|
||||
return existed.id;
|
||||
}
|
||||
|
||||
const newId = this.generateUniqueId('a', this.aircrafts);
|
||||
const aircraft: Aircraft = {
|
||||
id: newId,
|
||||
name: aircraftName,
|
||||
projectId
|
||||
};
|
||||
this.aircrafts.push(aircraft);
|
||||
|
||||
const projectPath = this.projectPaths.get(projectId);
|
||||
if (!projectPath) {
|
||||
return newId;
|
||||
}
|
||||
|
||||
const aircraftDir = path.join(projectPath, aircraftName);
|
||||
if (!fs.existsSync(aircraftDir)) {
|
||||
// 目录不存在就不再解析子目录
|
||||
return newId;
|
||||
}
|
||||
|
||||
// 每个子目录视为一个容器(排除 .git)
|
||||
const entries = await fs.promises.readdir(aircraftDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
if (entry.name === '.git') continue;
|
||||
|
||||
await this.importContainerFromExistingFolder(newId, entry.name);
|
||||
}
|
||||
|
||||
return newId;
|
||||
}
|
||||
|
||||
// =============== 容器相关方法 ===============
|
||||
|
||||
/**
|
||||
* ⚠️ 这里不再做 .git 过滤:
|
||||
* - 我们只会通过 createContainer / importContainerFromExistingFolder 创建容器
|
||||
* - importContainerFromExistingFolder 内部已经排除了 .git
|
||||
* 所以不会出现名字叫 ".git" 的容器
|
||||
*/
|
||||
getContainersByAircraft(aircraftId: string): Container[] {
|
||||
return this.containers.filter(c => c.aircraftId === aircraftId);
|
||||
}
|
||||
@@ -154,6 +202,7 @@ export class ProjectService {
|
||||
this.containers.push(newContainer);
|
||||
|
||||
await this.createContainerDirectory(newContainer);
|
||||
// UI 手动创建的容器,仍然保留“默认两个配置”的行为
|
||||
await this.createDefaultConfigs(newContainer);
|
||||
|
||||
return newId;
|
||||
@@ -179,6 +228,83 @@ export class ProjectService {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从已存在的磁盘目录导入容器
|
||||
*
|
||||
* ✅ 你的最新需求:
|
||||
* - 不创建“默认两个配置”
|
||||
* - 自动扫描容器目录下的『子文件夹』
|
||||
* - 每个子文件夹创建一个 ModuleFolder(type: 'local')
|
||||
* - 排除 `.git` 子文件夹
|
||||
*/
|
||||
async importContainerFromExistingFolder(aircraftId: string, containerName: string): Promise<string> {
|
||||
if (containerName === '.git') {
|
||||
throw new Error('不能将 .git 导入为容器');
|
||||
}
|
||||
|
||||
const existed = this.getContainersByAircraft(aircraftId).find(c => c.name === containerName);
|
||||
if (existed) {
|
||||
return existed.id;
|
||||
}
|
||||
|
||||
const newId = this.generateUniqueId('c', this.containers);
|
||||
const container: Container = {
|
||||
id: newId,
|
||||
name: containerName,
|
||||
aircraftId
|
||||
};
|
||||
this.containers.push(container);
|
||||
|
||||
// 🚩 关键逻辑:扫描容器目录下的子文件夹 -> 创建 ModuleFolder
|
||||
await this.scanContainerModuleFolders(container);
|
||||
|
||||
// 不再创建默认两个配置(不调用 createDefaultConfigs)
|
||||
// 也不创建目录(目录是 Git 克隆出来的,本来就存在)
|
||||
|
||||
return newId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描容器目录中的子文件夹(不含 .git),将其作为本地模块文件夹记录
|
||||
*/
|
||||
private async scanContainerModuleFolders(container: Container): Promise<void> {
|
||||
try {
|
||||
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
|
||||
if (!aircraft) return;
|
||||
|
||||
const projectPath = this.projectPaths.get(aircraft.projectId);
|
||||
if (!projectPath) return;
|
||||
|
||||
const containerDir = path.join(projectPath, aircraft.name, container.name);
|
||||
if (!fs.existsSync(containerDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entries = await fs.promises.readdir(containerDir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
if (entry.name === '.git') continue;
|
||||
|
||||
const folderId = this.generateUniqueId('local-', this.moduleFolders);
|
||||
const relativePath = `/${aircraft.projectId}/${aircraft.name}/${container.name}/${entry.name}`;
|
||||
|
||||
const moduleFolder: ModuleFolder = {
|
||||
id: folderId,
|
||||
name: entry.name,
|
||||
type: 'local',
|
||||
localPath: relativePath,
|
||||
containerId: container.id
|
||||
};
|
||||
|
||||
this.moduleFolders.push(moduleFolder);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('扫描容器目录生成模块文件夹失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// =============== 配置相关方法 ===============
|
||||
|
||||
getConfigsByContainer(containerId: string): Config[] {
|
||||
@@ -329,6 +455,10 @@ export class ProjectService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅用于「新建容器」时的默认两个配置
|
||||
* (导入容器时不会调用)
|
||||
*/
|
||||
private async createDefaultConfigs(container: Container): Promise<void> {
|
||||
this.configs.push({
|
||||
id: this.generateUniqueId('cfg', this.configs),
|
||||
@@ -389,10 +519,12 @@ export class ProjectService {
|
||||
this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId));
|
||||
this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId));
|
||||
|
||||
// 加载新数据
|
||||
// ⭐ 载入新数据时顺便过滤掉名字为 ".git" 的容器(避免历史数据带进来)
|
||||
const cleanedContainers = data.containers.filter(c => c.name !== '.git');
|
||||
|
||||
this.projects.push(...data.projects);
|
||||
this.aircrafts.push(...data.aircrafts);
|
||||
this.containers.push(...data.containers);
|
||||
this.containers.push(...cleanedContainers);
|
||||
this.configs.push(...data.configs);
|
||||
this.moduleFolders.push(...data.moduleFolders);
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// src/panels/views/AircraftView.ts
|
||||
import { BaseView } from './BaseView';
|
||||
import { AircraftViewData } from '../types/ViewTypes';
|
||||
|
||||
@@ -27,6 +26,7 @@ export class AircraftView extends BaseView {
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>飞行器配置</title>
|
||||
${this.getStyles()}
|
||||
${this.getRepoSelectScript()}
|
||||
<style>
|
||||
.header {
|
||||
display: flex;
|
||||
@@ -60,7 +60,7 @@ export class AircraftView extends BaseView {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin: 0 auto;
|
||||
/* ❌ 原来这里有 margin: 0 auto; 已去掉,避免“获取仓库”按钮被居中 */
|
||||
}
|
||||
.btn-new:hover {
|
||||
background: var(--vscode-button-hoverBackground);
|
||||
@@ -78,6 +78,81 @@ export class AircraftView extends BaseView {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 仓库 + 分支树公共样式 */
|
||||
.config-section {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.section-title {
|
||||
margin: 30px 0 15px 0;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid var(--vscode-panel-border);
|
||||
color: var(--vscode-titleBar-activeForeground);
|
||||
}
|
||||
.url-input-section {
|
||||
background: var(--vscode-panel-background);
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
}
|
||||
.branch-selection {
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
}
|
||||
.branch-tree {
|
||||
font-family: 'Courier New', monospace;
|
||||
user-select: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.branch-node {
|
||||
padding: 2px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.branch-node:hover {
|
||||
background: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
.branch-icon {
|
||||
margin-right: 4px;
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
.branch-expand {
|
||||
margin-right: 2px;
|
||||
cursor: pointer;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
.branch-checkbox {
|
||||
margin-right: 6px;
|
||||
}
|
||||
.branch-name {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
}
|
||||
.branch-children {
|
||||
margin-left: 16px;
|
||||
border-left: 1px solid var(--vscode-panel-border);
|
||||
padding-left: 8px;
|
||||
}
|
||||
.branch-leaf {
|
||||
margin-left: 16px;
|
||||
}
|
||||
.current-branch {
|
||||
color: var(--vscode-gitDecoration-untrackedResourceForeground);
|
||||
font-weight: bold;
|
||||
}
|
||||
.tree-icon {
|
||||
font-size: 2em;
|
||||
}
|
||||
.branch-tree-title {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -104,6 +179,21 @@ export class AircraftView extends BaseView {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 飞行器云仓库 -->
|
||||
<div class="config-section">
|
||||
<h3 class="section-title">🛫 飞行器云仓库</h3>
|
||||
<div class="url-input-section">
|
||||
<h4>🔗 获取飞行器仓库</h4>
|
||||
<div style="display: flex; gap: 10px; margin-top: 10px; align-items: center;">
|
||||
<button class="btn-new" onclick="openRepoSelectForAircraft()">获取仓库</button>
|
||||
<span style="font-size: 12px; color: var(--vscode-descriptionForeground);">
|
||||
从仓库配置中选择 Git 仓库,选择分支后可将飞行器代码克隆到当前项目下(以分支名作为飞行器名称)
|
||||
</span>
|
||||
</div>
|
||||
<div id="branchSelectionContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const vscode = acquireVsCodeApi();
|
||||
|
||||
@@ -184,6 +274,164 @@ export class AircraftView extends BaseView {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ======== Git 仓库 & 分支树 - AircraftView 作用域 ========
|
||||
|
||||
let selectedBranches = new Set();
|
||||
let branchTreeData = [];
|
||||
|
||||
function openRepoSelectForAircraft() {
|
||||
vscode.postMessage({
|
||||
type: 'openRepoSelectForAircraft'
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBranch(branchName) {
|
||||
const checkbox = document.getElementById('branch-' + branchName.replace(/[^a-zA-Z0-9-]/g, '-'));
|
||||
if (checkbox && checkbox.checked) {
|
||||
selectedBranches.add(branchName);
|
||||
} else {
|
||||
selectedBranches.delete(branchName);
|
||||
}
|
||||
}
|
||||
|
||||
function cloneSelectedBranches() {
|
||||
if (selectedBranches.size === 0) {
|
||||
alert('请至少选择一个分支');
|
||||
return;
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: 'cloneBranches',
|
||||
branches: Array.from(selectedBranches)
|
||||
});
|
||||
|
||||
selectedBranches.clear();
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||
document.getElementById('branchSelectionContainer').innerHTML = '';
|
||||
}
|
||||
|
||||
function cancelBranchSelection() {
|
||||
selectedBranches.clear();
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||
document.getElementById('branchSelectionContainer').innerHTML = '';
|
||||
vscode.postMessage({
|
||||
type: 'cancelBranchSelection'
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBranchNode(nodeId) {
|
||||
const node = findNodeById(branchTreeData, nodeId);
|
||||
if (node && !node.isLeaf) {
|
||||
node.expanded = !node.expanded;
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
}
|
||||
|
||||
function expandAllBranches() {
|
||||
setAllExpanded(branchTreeData, true);
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
|
||||
function collapseAllBranches() {
|
||||
setAllExpanded(branchTreeData, false);
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
|
||||
function setAllExpanded(nodes, expanded) {
|
||||
nodes.forEach(node => {
|
||||
if (!node.isLeaf) {
|
||||
node.expanded = expanded;
|
||||
if (node.children) {
|
||||
setAllExpanded(node.children, expanded);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function findNodeById(nodes, nodeId) {
|
||||
for (const node of nodes) {
|
||||
if (node.fullName === nodeId) return node;
|
||||
if (node.children) {
|
||||
const found = findNodeById(node.children, nodeId);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderBranchTree(treeData) {
|
||||
const container = document.getElementById('branchSelectionContainer');
|
||||
|
||||
if (!treeData || treeData.length === 0) {
|
||||
container.innerHTML = '<div style="text-align: center; padding: 10px; color: var(--vscode-descriptionForeground);">未找到分支</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">';
|
||||
html += '<h4 class="branch-tree-title" style="margin: 0 0 8px 0;"><span class="tree-icon">🌳</span> 分支树</h4>';
|
||||
|
||||
html += '<div style="margin-bottom: 8px;">';
|
||||
html += '<button class="back-btn" onclick="expandAllBranches()" style="margin-right: 8px; padding: 2px 6px; font-size: 11px;">展开全部</button>';
|
||||
html += '<button class="back-btn" onclick="collapseAllBranches()" style="padding: 2px 6px; font-size: 11px;">收起全部</button>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="branch-tree">';
|
||||
html += renderBranchNodes(treeData, 0);
|
||||
html += '</div>';
|
||||
|
||||
html += '<div style="margin-top: 12px;">';
|
||||
html += '<button class="btn-new" onclick="cloneSelectedBranches()" style="margin-right: 8px; padding: 4px 8px; font-size: 12px;">✅ 克隆选中分支</button>';
|
||||
html += '<button class="back-btn" onclick="cancelBranchSelection()" style="padding: 4px 8px; font-size: 12px;">取消</button>';
|
||||
html += '</div></div>';
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderBranchNodes(nodes, level) {
|
||||
let html = '';
|
||||
nodes.forEach(node => {
|
||||
const indent = level * 20;
|
||||
const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
|
||||
if (node.isLeaf) {
|
||||
html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">';
|
||||
html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" ';
|
||||
html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\\'' + node.fullName + '\\')">';
|
||||
html += '<span class="branch-icon">🌿</span>';
|
||||
html += '<span class="branch-name ' + (node.branch && node.branch.isCurrent ? 'current-branch' : '') + '">';
|
||||
html += node.name;
|
||||
html += (node.branch && node.branch.isCurrent ? ' ⭐' : '');
|
||||
html += '</span>';
|
||||
html += '</div>';
|
||||
} else {
|
||||
html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">';
|
||||
html += '<span class="branch-expand" onclick="toggleBranchNode(\\'' + node.fullName + '\\')">';
|
||||
html += (node.expanded ? '🪵' : '🪵');
|
||||
html += '</span>';
|
||||
html += '<span class="branch-icon">🪵</span>';
|
||||
html += '<span class="branch-name">' + node.name + '</span>';
|
||||
html += '</div>';
|
||||
|
||||
if (node.expanded && node.children && node.children.length > 0) {
|
||||
html += '<div class="branch-children">';
|
||||
html += renderBranchNodes(node.children, level + 1);
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
window.addEventListener('message', event => {
|
||||
const message = event.data;
|
||||
if (message.type === 'branchesFetched') {
|
||||
branchTreeData = message.branchTree || [];
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// src/panels/views/ContainerView.ts
|
||||
import { BaseView } from './BaseView';
|
||||
import { AircraftConfigData, ContainerViewData } from '../types/ViewTypes';
|
||||
|
||||
@@ -29,6 +28,117 @@ export class ContainerView extends BaseView {
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>容器管理</title>
|
||||
${this.getStyles()}
|
||||
${this.getRepoSelectScript()}
|
||||
<style>
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.back-btn {
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.back-btn:hover {
|
||||
background: var(--vscode-button-secondaryHoverBackground);
|
||||
}
|
||||
.btn-new {
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
/* ❌ 原来这里有 margin: 0 auto; 已去掉,避免“获取仓库”按钮被居中 */
|
||||
}
|
||||
.btn-new:hover {
|
||||
background: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
|
||||
/* 仓库 + 分支树公共样式 */
|
||||
.config-section {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.section-title {
|
||||
margin: 30px 0 15px 0;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid var(--vscode-panel-border);
|
||||
color: var(--vscode-titleBar-activeForeground);
|
||||
}
|
||||
.url-input-section {
|
||||
background: var(--vscode-panel-background);
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
}
|
||||
.branch-selection {
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
}
|
||||
.branch-tree {
|
||||
font-family: 'Courier New', monospace;
|
||||
user-select: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.branch-node {
|
||||
padding: 2px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.branch-node:hover {
|
||||
background: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
.branch-icon {
|
||||
margin-right: 4px;
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
.branch-expand {
|
||||
margin-right: 2px;
|
||||
cursor: pointer;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
.branch-checkbox {
|
||||
margin-right: 6px;
|
||||
}
|
||||
.branch-name {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
}
|
||||
.branch-children {
|
||||
margin-left: 16px;
|
||||
border-left: 1px solid var(--vscode-panel-border);
|
||||
padding-left: 8px;
|
||||
}
|
||||
.branch-leaf {
|
||||
margin-left: 16px;
|
||||
}
|
||||
.current-branch {
|
||||
color: var(--vscode-gitDecoration-untrackedResourceForeground);
|
||||
font-weight: bold;
|
||||
}
|
||||
.tree-icon {
|
||||
font-size: 2em;
|
||||
}
|
||||
.branch-tree-title {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
@@ -54,6 +164,21 @@ export class ContainerView extends BaseView {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 容器云仓库 -->
|
||||
<div class="config-section">
|
||||
<h3 class="section-title">📦 容器云仓库</h3>
|
||||
<div class="url-input-section">
|
||||
<h4>🔗 获取容器仓库</h4>
|
||||
<div style="display: flex; gap: 10px; margin-top: 10px; align-items: center;">
|
||||
<button class="btn-new" onclick="openRepoSelectForContainer()">获取仓库</button>
|
||||
<span style="font-size: 12px; color: var(--vscode-descriptionForeground);">
|
||||
从仓库配置中选择 Git 仓库,选择分支后可将容器代码克隆到当前飞行器下(以分支名作为容器名称)
|
||||
</span>
|
||||
</div>
|
||||
<div id="branchSelectionContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const vscode = acquireVsCodeApi();
|
||||
|
||||
@@ -116,6 +241,164 @@ export class ContainerView extends BaseView {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ======== Git 仓库 & 分支树 - ContainerView 作用域 ========
|
||||
|
||||
let selectedBranches = new Set();
|
||||
let branchTreeData = [];
|
||||
|
||||
function openRepoSelectForContainer() {
|
||||
vscode.postMessage({
|
||||
type: 'openRepoSelectForContainer'
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBranch(branchName) {
|
||||
const checkbox = document.getElementById('branch-' + branchName.replace(/[^a-zA-Z0-9-]/g, '-'));
|
||||
if (checkbox && checkbox.checked) {
|
||||
selectedBranches.add(branchName);
|
||||
} else {
|
||||
selectedBranches.delete(branchName);
|
||||
}
|
||||
}
|
||||
|
||||
function cloneSelectedBranches() {
|
||||
if (selectedBranches.size === 0) {
|
||||
alert('请至少选择一个分支');
|
||||
return;
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: 'cloneBranches',
|
||||
branches: Array.from(selectedBranches)
|
||||
});
|
||||
|
||||
selectedBranches.clear();
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||
document.getElementById('branchSelectionContainer').innerHTML = '';
|
||||
}
|
||||
|
||||
function cancelBranchSelection() {
|
||||
selectedBranches.clear();
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||
document.getElementById('branchSelectionContainer').innerHTML = '';
|
||||
vscode.postMessage({
|
||||
type: 'cancelBranchSelection'
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBranchNode(nodeId) {
|
||||
const node = findNodeById(branchTreeData, nodeId);
|
||||
if (node && !node.isLeaf) {
|
||||
node.expanded = !node.expanded;
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
}
|
||||
|
||||
function expandAllBranches() {
|
||||
setAllExpanded(branchTreeData, true);
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
|
||||
function collapseAllBranches() {
|
||||
setAllExpanded(branchTreeData, false);
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
|
||||
function setAllExpanded(nodes, expanded) {
|
||||
nodes.forEach(node => {
|
||||
if (!node.isLeaf) {
|
||||
node.expanded = expanded;
|
||||
if (node.children) {
|
||||
setAllExpanded(node.children, expanded);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function findNodeById(nodes, nodeId) {
|
||||
for (const node of nodes) {
|
||||
if (node.fullName === nodeId) return node;
|
||||
if (node.children) {
|
||||
const found = findNodeById(node.children, nodeId);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderBranchTree(treeData) {
|
||||
const container = document.getElementById('branchSelectionContainer');
|
||||
|
||||
if (!treeData || treeData.length === 0) {
|
||||
container.innerHTML = '<div style="text-align: center; padding: 10px; color: var(--vscode-descriptionForeground);">未找到分支</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">';
|
||||
html += '<h4 class="branch-tree-title" style="margin: 0 0 8px 0;"><span class="tree-icon">🌳</span> 分支树</h4>';
|
||||
|
||||
html += '<div style="margin-bottom: 8px;">';
|
||||
html += '<button class="back-btn" onclick="expandAllBranches()" style="margin-right: 8px; padding: 2px 6px; font-size: 11px;">展开全部</button>';
|
||||
html += '<button class="back-btn" onclick="collapseAllBranches()" style="padding: 2px 6px; font-size: 11px;">收起全部</button>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="branch-tree">';
|
||||
html += renderBranchNodes(treeData, 0);
|
||||
html += '</div>';
|
||||
|
||||
html += '<div style="margin-top: 12px;">';
|
||||
html += '<button class="btn-new" onclick="cloneSelectedBranches()" style="margin-right: 8px; padding: 4px 8px; font-size: 12px;">✅ 克隆选中分支</button>';
|
||||
html += '<button class="back-btn" onclick="cancelBranchSelection()" style="padding: 4px 8px; font-size: 12px;">取消</button>';
|
||||
html += '</div></div>';
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderBranchNodes(nodes, level) {
|
||||
let html = '';
|
||||
nodes.forEach(node => {
|
||||
const indent = level * 20;
|
||||
const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
|
||||
if (node.isLeaf) {
|
||||
html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">';
|
||||
html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" ';
|
||||
html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\\'' + node.fullName + '\\')">';
|
||||
html += '<span class="branch-icon">🌿</span>';
|
||||
html += '<span class="branch-name ' + (node.branch && node.branch.isCurrent ? 'current-branch' : '') + '">';
|
||||
html += node.name;
|
||||
html += (node.branch && node.branch.isCurrent ? ' ⭐' : '');
|
||||
html += '</span>';
|
||||
html += '</div>';
|
||||
} else {
|
||||
html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">';
|
||||
html += '<span class="branch-expand" onclick="toggleBranchNode(\\'' + node.fullName + '\\')">';
|
||||
html += (node.expanded ? '🪵' : '🪵');
|
||||
html += '</span>';
|
||||
html += '<span class="branch-icon">🪵</span>';
|
||||
html += '<span class="branch-name">' + node.name + '</span>';
|
||||
html += '</div>';
|
||||
|
||||
if (node.expanded && node.children && node.children.length > 0) {
|
||||
html += '<div class="branch-children">';
|
||||
html += renderBranchNodes(node.children, level + 1);
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
window.addEventListener('message', event => {
|
||||
const message = event.data;
|
||||
if (message.type === 'branchesFetched') {
|
||||
branchTreeData = message.branchTree || [];
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// src/panels/views/ProjectView.ts
|
||||
import { BaseView } from './BaseView';
|
||||
import { ProjectViewData } from '../types/ViewTypes';
|
||||
|
||||
@@ -38,6 +37,7 @@ export class ProjectView extends BaseView {
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>数字卫星构建平台</title>
|
||||
${this.getStyles()}
|
||||
${this.getRepoSelectScript()}
|
||||
<style>
|
||||
.satellite-icon {
|
||||
font-size: 2.5em;
|
||||
@@ -84,15 +84,102 @@ export class ProjectView extends BaseView {
|
||||
.btn-action:hover {
|
||||
background: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
/* 统一“主要操作按钮”的样式 */
|
||||
.btn-new {
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.btn-new:hover {
|
||||
background: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
.btn-open {
|
||||
background: var(--vscode-input-background);
|
||||
color: var(--vscode-input-foreground);
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
}
|
||||
|
||||
/* 仓库 & 分支树公共样式 */
|
||||
.config-section {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.section-title {
|
||||
margin: 30px 0 15px 0;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid var(--vscode-panel-border);
|
||||
color: var(--vscode-titleBar-activeForeground);
|
||||
}
|
||||
.url-input-section {
|
||||
background: var(--vscode-panel-background);
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
}
|
||||
.branch-selection {
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
}
|
||||
.branch-tree {
|
||||
font-family: 'Courier New', monospace;
|
||||
user-select: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.branch-node {
|
||||
padding: 2px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.branch-node:hover {
|
||||
background: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
.branch-icon {
|
||||
margin-right: 4px;
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
.branch-expand {
|
||||
margin-right: 2px;
|
||||
cursor: pointer;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
.branch-checkbox {
|
||||
margin-right: 6px;
|
||||
}
|
||||
.branch-name {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
}
|
||||
.branch-children {
|
||||
margin-left: 16px;
|
||||
border-left: 1px solid var(--vscode-panel-border);
|
||||
padding-left: 8px;
|
||||
}
|
||||
.branch-leaf {
|
||||
margin-left: 16px;
|
||||
}
|
||||
.current-branch {
|
||||
color: var(--vscode-gitDecoration-untrackedResourceForeground);
|
||||
font-weight: bold;
|
||||
}
|
||||
.tree-icon {
|
||||
font-size: 2em;
|
||||
}
|
||||
.branch-tree-title {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -126,6 +213,21 @@ export class ProjectView extends BaseView {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 项目云仓库 -->
|
||||
<div class="config-section">
|
||||
<h3 class="section-title">📚 项目云仓库</h3>
|
||||
<div class="url-input-section">
|
||||
<h4>🔗 获取项目仓库</h4>
|
||||
<div style="display: flex; gap: 10px; margin-top: 10px; align-items: center;">
|
||||
<button class="btn-new" onclick="openRepoSelectForProject()">获取仓库</button>
|
||||
<span style="font-size: 12px; color: var(--vscode-descriptionForeground);">
|
||||
从仓库配置中选择 Git 仓库,选择分支后可将完整项目克隆到本地(包含 dscp-data.json)
|
||||
</span>
|
||||
</div>
|
||||
<div id="branchSelectionContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const vscode = acquireVsCodeApi();
|
||||
|
||||
@@ -219,6 +321,164 @@ export class ProjectView extends BaseView {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ======== Git 仓库 & 分支树 - ProjectView 作用域 ========
|
||||
|
||||
let selectedBranches = new Set();
|
||||
let branchTreeData = [];
|
||||
|
||||
function openRepoSelectForProject() {
|
||||
vscode.postMessage({
|
||||
type: 'openRepoSelectForProject'
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBranch(branchName) {
|
||||
const checkbox = document.getElementById('branch-' + branchName.replace(/[^a-zA-Z0-9-]/g, '-'));
|
||||
if (checkbox && checkbox.checked) {
|
||||
selectedBranches.add(branchName);
|
||||
} else {
|
||||
selectedBranches.delete(branchName);
|
||||
}
|
||||
}
|
||||
|
||||
function cloneSelectedBranches() {
|
||||
if (selectedBranches.size === 0) {
|
||||
alert('请至少选择一个分支');
|
||||
return;
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: 'cloneBranches',
|
||||
branches: Array.from(selectedBranches)
|
||||
});
|
||||
|
||||
selectedBranches.clear();
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||
document.getElementById('branchSelectionContainer').innerHTML = '';
|
||||
}
|
||||
|
||||
function cancelBranchSelection() {
|
||||
selectedBranches.clear();
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => checkbox.checked = false);
|
||||
document.getElementById('branchSelectionContainer').innerHTML = '';
|
||||
vscode.postMessage({
|
||||
type: 'cancelBranchSelection'
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBranchNode(nodeId) {
|
||||
const node = findNodeById(branchTreeData, nodeId);
|
||||
if (node && !node.isLeaf) {
|
||||
node.expanded = !node.expanded;
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
}
|
||||
|
||||
function expandAllBranches() {
|
||||
setAllExpanded(branchTreeData, true);
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
|
||||
function collapseAllBranches() {
|
||||
setAllExpanded(branchTreeData, false);
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
|
||||
function setAllExpanded(nodes, expanded) {
|
||||
nodes.forEach(node => {
|
||||
if (!node.isLeaf) {
|
||||
node.expanded = expanded;
|
||||
if (node.children) {
|
||||
setAllExpanded(node.children, expanded);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function findNodeById(nodes, nodeId) {
|
||||
for (const node of nodes) {
|
||||
if (node.fullName === nodeId) return node;
|
||||
if (node.children) {
|
||||
const found = findNodeById(node.children, nodeId);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderBranchTree(treeData) {
|
||||
const container = document.getElementById('branchSelectionContainer');
|
||||
|
||||
if (!treeData || treeData.length === 0) {
|
||||
container.innerHTML = '<div style="text-align: center; padding: 10px; color: var(--vscode-descriptionForeground);">未找到分支</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">';
|
||||
html += '<h4 class="branch-tree-title" style="margin: 0 0 8px 0;"><span class="tree-icon">🌳</span> 分支树</h4>';
|
||||
|
||||
html += '<div style="margin-bottom: 8px;">';
|
||||
html += '<button class="back-btn" onclick="expandAllBranches()" style="margin-right: 8px; padding: 2px 6px; font-size: 11px;">展开全部</button>';
|
||||
html += '<button class="back-btn" onclick="collapseAllBranches()" style="padding: 2px 6px; font-size: 11px;">收起全部</button>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="branch-tree">';
|
||||
html += renderBranchNodes(treeData, 0);
|
||||
html += '</div>';
|
||||
|
||||
html += '<div style="margin-top: 12px;">';
|
||||
html += '<button class="btn-new" onclick="cloneSelectedBranches()" style="margin-right: 8px; padding: 4px 8px; font-size: 12px;">✅ 克隆选中分支</button>';
|
||||
html += '<button class="back-btn" onclick="cancelBranchSelection()" style="padding: 4px 8px; font-size: 12px;">取消</button>';
|
||||
html += '</div></div>';
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderBranchNodes(nodes, level) {
|
||||
let html = '';
|
||||
nodes.forEach(node => {
|
||||
const indent = level * 20;
|
||||
const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
|
||||
if (node.isLeaf) {
|
||||
html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">';
|
||||
html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" ';
|
||||
html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\\'' + node.fullName + '\\')">';
|
||||
html += '<span class="branch-icon">🌿</span>';
|
||||
html += '<span class="branch-name ' + (node.branch && node.branch.isCurrent ? 'current-branch' : '') + '">';
|
||||
html += node.name;
|
||||
html += (node.branch && node.branch.isCurrent ? ' ⭐' : '');
|
||||
html += '</span>';
|
||||
html += '</div>';
|
||||
} else {
|
||||
html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">';
|
||||
html += '<span class="branch-expand" onclick="toggleBranchNode(\\'' + node.fullName + '\\')">';
|
||||
html += (node.expanded ? '🪵' : '🪵');
|
||||
html += '</span>';
|
||||
html += '<span class="branch-icon">🪵</span>';
|
||||
html += '<span class="branch-name">' + node.name + '</span>';
|
||||
html += '</div>';
|
||||
|
||||
if (node.expanded && node.children && node.children.length > 0) {
|
||||
html += '<div class="branch-children">';
|
||||
html += renderBranchNodes(node.children, level + 1);
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
window.addEventListener('message', event => {
|
||||
const message = event.data;
|
||||
if (message.type === 'branchesFetched') {
|
||||
branchTreeData = message.branchTree || [];
|
||||
renderBranchTree(branchTreeData);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
Reference in New Issue
Block a user