0
0

初步给项目、飞行器、容器页面添加了git拉取功能

This commit is contained in:
xubing
2025-12-05 20:17:05 +08:00
parent a7edfb82c5
commit 2c314b9b0b
19 changed files with 2557 additions and 156 deletions

View File

@@ -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"
}
]
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
/**
* 从已存在的磁盘目录导入容器
*
* ✅ 你的最新需求:
* - 不创建“默认两个配置”
* - 自动扫描容器目录下的『子文件夹』
* - 每个子文件夹创建一个 ModuleFoldertype: '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

View File

@@ -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>`;

View File

@@ -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"}

View File

@@ -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>`;

View File

@@ -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"}

View File

@@ -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>`;

View File

@@ -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"}

File diff suppressed because it is too large Load Diff

13
src/panels/services/GitService.ts Normal file → Executable file
View 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);

View File

@@ -31,7 +31,7 @@ export class ProjectService {
}
// =============== 项目相关方法 ===============
getProjects(): Project[] {
return this.projects;
}
@@ -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;
}
/**
* 从已存在的磁盘目录导入容器
*
* ✅ 你的最新需求:
* - 不创建“默认两个配置”
* - 自动扫描容器目录下的『子文件夹』
* - 每个子文件夹创建一个 ModuleFoldertype: '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);

View File

@@ -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,8 +274,166 @@ 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>`;
}
}
}

View File

@@ -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,8 +241,166 @@ 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>`;
}
}
}

View File

@@ -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,8 +321,166 @@ 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>`;
}
}
}