Files
yuding 4a09d52283 feat(clipping): implement hide/recover toggle for all section dialogs
Update all three section dialogs to support hide/show toggle:

SectionAxisDialogManager:
- onHideToggle now calls hideSection()/recoverSection()

SectionBoxDialogManager:
- onHideToggle now calls hideSection()/recoverSection()

SectionPlanePanel:
- Add isHidden state tracking
- Change onHide to onHideToggle(isHidden)
- Add setHiddenState/getHiddenState methods
- Update button to toggle active state

SectionPlaneDialogManager:
- Switch to onHideToggle callback
- Call hideSection()/recoverSection() based on toggle state

Behavior: Click hide button to hide section, click again to recover.
2026-02-02 16:36:17 +08:00

542 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Learnings: 构件详情右键菜单功能
## 2026-01-28 - Implementation Complete
### Architecture Patterns
#### 1. Event-Driven Component Selection
**Pattern**: 底层引擎触发事件 → SDK 监听并维护状态
```typescript
// SDK 层监听底层事件
this.engine.events.on('click', (hit: any) => {
if (hit && hit.object) {
this.selectedComponent = { url: hit.object.url, id: hit.object.name };
} else {
this.selectedComponent = null;
}
});
```
**Key Insight**: SDK 不需要主动查询状态,而是被动监听底层事件,降低耦合度。
---
#### 2. Dynamic Menu Generation
**Pattern**: 注册处理器返回函数,运行时动态生成菜单
```typescript
// 注册时传入函数,而非静态配置
rightKey.registerHandler((_e) => {
const selected = this.getSelectedComponent();
const items: MenuItemConfig[] = [];
// 根据当前状态动态构建菜单
if (selected) {
items.push({ /* 构件详情 */ });
}
items.push({ /* 显示全部 */ });
return items;
});
```
**Key Insight**: 右键菜单内容不是静态的,而是根据运行时状态(选中/未选中)动态生成,提供上下文感知的用户体验。
---
#### 3. Manager Registry Pattern
**Pattern**: 单例注册表 + 代理方法
```typescript
// Registry 存储所有 Manager 实例
registry.engine3d = this.engine;
registry.componentDetail = this.componentDetail;
// 其他 Manager 通过 Registry 访问
const selected = registry.engine3d?.getSelectedComponent();
registry.componentDetail?.show(selected.url, selected.id);
```
**Key Insight**:
- 避免 Manager 之间直接引用,降低耦合
- 统一访问入口,便于管理和调试
- 支持延迟初始化nullable 类型)
---
#### 4. Data Transformation Layer
**Pattern**: 底层数据 → SDK 转换 → UI 展示
```typescript
// 底层返回格式
{
properties: [{
name: "分类名",
children: [{ name: "属性名", value: "值" }]
}]
}
// SDK 转换为 UI 组件格式
const categories = data.properties.map(cat => ({
categoryName: cat.name,
items: cat.children.map(child => ({
key: child.name,
value: child.value
}))
}));
// UI 组件渲染
BimCollapse({ items: categories })
BimDescription({ items: category.items })
```
**Key Insight**: SDK 作为中间层,负责数据格式转换,使 UI 组件专注于展示逻辑。
---
### Code Conventions
#### 1. Console Logging
```typescript
// 中文日志 + Manager 前缀
console.log('[Engine] 构件选中:', this.selectedComponent);
console.log('[ComponentDetailManager] 显示构件详情');
console.error('[EngineManager] Engine 尚未初始化');
```
**Reason**:
- 中文便于业务人员理解
- 前缀便于定位代码位置
- 区分 log/warn/error 级别
---
#### 2. Section Markers
```typescript
// ==================== 选中状态管理 ====================
private selectedComponent: { url: string; id: string } | null = null;
public getSelectedComponent(): { url: string; id: string } | null {
return this.selectedComponent;
}
```
**Reason**: 大文件中用分隔符标记功能区域,提升可读性。
---
#### 3. JSDoc Comments
```typescript
/**
* 获取当前选中的构件信息
* @returns 选中的构件信息,包含 url 和 id未选中时返回 null
*/
public getSelectedComponent(): { url: string; id: string } | null {
return this.selectedComponent;
}
```
**Reason**:
- 只对 public 方法添加 JSDoc
- 描述功能、参数、返回值
- 符合现有代码风格
---
### TypeScript Patterns
#### 1. Optional Chaining + Nullish Coalescing
```typescript
// 安全访问 + 默认值
return this.engineInstance?.getSelectedComponent() ?? null;
// 条件调用
registry.componentDetail?.show(url, id);
```
**Reason**:
- Manager 可能未初始化
- 优雅处理 null/undefined
---
#### 2. Type-Safe Event Callbacks
```typescript
// 明确回调参数类型
public getComponentProperties(
url: string,
id: string,
callback: (data: any) => void // 底层未定义类型,先用 any
): void {
this.engine.modelProperties.getModelProperties(url, id, callback);
}
```
**Trade-off**: 底层 API 未提供 TypeScript 类型定义,暂用 `any`,后续可补充。
---
### i18n Patterns
#### 1. Structured Translation Keys
```typescript
// 按功能分组
menu: {
componentDetail: '构件详情',
showAll: '显示全部'
}
panel: {
componentDetail: {
title: '构件详情'
}
}
```
**Reason**:
- 层级结构便于管理
- 避免命名冲突
- 易于扩展
---
#### 2. Type-Safe Translation
```typescript
// 先定义类型
interface TranslationDictionary {
menu: {
componentDetail: string;
showAll: string;
};
}
// 再实现翻译
export const zhCN: TranslationDictionary = { ... };
export const enUS: TranslationDictionary = { ... };
```
**Reason**: TypeScript 编译时检查,防止遗漏翻译。
---
### Documentation Patterns
#### 1. Multi-Chapter Structure
```
第一章:工具栏 (Toolbar)
第二章:右键菜单 (Context Menu)
第三章:构件交互 (Component Interaction)
```
**Reason**:
- 从功能维度到文档维度的升级
- 支持未来扩展(第四章、第五章...
- 保持现有内容不变,降低风险
---
#### 2. Call Chain Visualization
```
用户点击构件
[底层] interactionModule.handleMouseClick()
[SDK] Engine: click event listener
[SDK] EngineManager: getSelectedComponent()
[UI] RightKeyManager: dynamic menu
```
**Reason**:
- 清晰展示调用层级
- 区分底层/SDK/UI 边界
- 便于新人理解架构
---
### Challenges & Solutions
#### Challenge 1: 右键菜单如何知道是否有选中构件?
**Solution**:
- Engine 组件监听底层 click 事件,维护 `selectedComponent` 状态
- EngineManager 暴露 `getSelectedComponent()` 方法
- RightKeyManager 的处理器函数每次运行时调用此方法
**Key Decision**: 状态存储在 Engine而非 EngineManager因为 Engine 直接监听底层事件。
---
#### Challenge 2: 如何避免循环依赖?
**Problem**:
- ComponentDetailManager 需要调用 EngineManager
- EngineManager 的菜单需要调用 ComponentDetailManager
**Solution**:
- 使用 ManagerRegistry 单例作为中介
- 两者都不直接引用对方,而是通过 Registry 访问
```typescript
// ComponentDetailManager
const registry = ManagerRegistry.getInstance();
registry.engine3d?.getComponentProperties(...);
// EngineManager
const registry = ManagerRegistry.getInstance();
registry.componentDetail?.show(...);
```
---
#### Challenge 3: 底层 API 数据格式与 UI 组件格式不一致
**Problem**:
- 底层返回: `properties: [{ name, children: [{ name, value }] }]`
- UI 需要: `items: [{ categoryName, items: [{ key, value }] }]`
**Solution**:
- ComponentDetailManager 中进行数据转换
- 职责明确Manager 负责数据适配Component 负责展示
```typescript
const categories = data.properties.map(cat => ({
categoryName: cat.name,
items: cat.children.map(child => ({
key: child.name,
value: child.value
}))
}));
```
---
### Build & Verification
#### Build Command
```bash
bun run build
```
**Output**:
```
✓ 87 modules transformed
dist/iflow-engine.es.js 2,025.42 kB
dist/iflow-engine.umd.js 1,329.90 kB
✓ built in 4.98s
```
**Verification Checklist**:
- [x] TypeScript 编译无错误
- [x] 项目级 LSP diagnostics 无错误
- [x] 模块打包成功ESM + UMD
- [x] 文件大小在合理范围
---
### Git Workflow
**Commit Strategy**: 每个任务独立提交
1. `cf20389` - Engine 监听点击事件
2. `e75886d` - EngineManager 动态菜单
3. `33f1c72` - ComponentDetailManager 实现
4. `89789e0` - Registry 注册
5. `a61c7f4` - i18n 国际化
**Benefits**:
- 每个 commit 职责单一
- 便于 code review
- 支持 cherry-pick / revert
- 清晰的历史记录
---
## Reusable Patterns for Future Work
### Pattern: Dynamic UI Based on Runtime State
**When to use**:
- Toolbar button visibility
- Menu item enable/disable
- Panel content switching
**Template**:
```typescript
// 1. State storage
private currentState: State | null = null;
// 2. State getter
public getState(): State | null {
return this.currentState;
}
// 3. Dynamic generator
const handler = () => {
const state = this.getState();
const items = [];
if (state?.condition) {
items.push({ /* conditional item */ });
}
items.push({ /* always visible item */ });
return items;
};
```
---
### Pattern: Manager Communication via Registry
**When to use**:
- Manager A needs to call Manager B
- Avoid circular dependencies
**Template**:
```typescript
// In Manager A
const registry = ManagerRegistry.getInstance();
registry.managerB?.doSomething();
// In Manager B
const registry = ManagerRegistry.getInstance();
registry.managerA?.getSomeData();
```
**Checklist**:
- [ ] Update ManagerRegistry type definition
- [ ] Register manager instance in BimEngine.init()
- [ ] Use optional chaining (`?.`) for all registry accesses
---
### Pattern: Bottom-Up Data Flow (Event-Driven)
**When to use**:
- 底层引擎有原生事件系统
- SDK 需要响应底层变化
**Template**:
```typescript
// 1. SDK 监听底层事件
this.engine.events.on('eventName', (payload) => {
// 2. 更新 SDK 状态
this.updateState(payload);
// 3. 可选:触发 SDK 层事件
registry.emit('sdk:eventName', transformedPayload);
});
```
---
## Gotchas
### Gotcha 1: Registry 访问时机
**Problem**: 在 Manager constructor 中访问 registry 可能为空
**Solution**: 在 `init()` 方法或延迟访问点使用 registry
```typescript
// ❌ Bad
constructor() {
const registry = ManagerRegistry.getInstance();
this.engine = registry.engine3d; // 可能为 null
}
// ✅ Good
public show() {
const registry = ManagerRegistry.getInstance();
registry.engine3d?.doSomething(); // 使用时访问
}
```
---
### Gotcha 2: 国际化类型定义先行
**Problem**: 直接在 zh-CN.ts 添加翻译会导致 TypeScript 错误
**Solution**: 先更新 types.ts再更新 zh-CN.ts 和 en-US.ts
**Order**:
1. `src/locales/types.ts` - 添加类型定义
2. `src/locales/zh-CN.ts` - 添加中文翻译
3. `src/locales/en-US.ts` - 添加英文翻译
---
### Gotcha 3: 文档 Read-Before-Write
**Problem**: Edit_tool 要求先 Read 文件才能编辑
**Solution**: 即使之前读过,每次 Edit 前也要 Read 一次
```typescript
// ✅ Correct workflow
1. Read_tool(file)
2. Edit_tool(file, oldString, newString)
3. Read_tool(file) // Next edit
4. Edit_tool(file, ...)
```
---
## Metrics
- **Files Modified**: 9
- **New File Created**: 1 (component-detail-manager.ts)
- **Lines Added**: ~500
- **Commits**: 5
- **Build Time**: ~5s
- **Implementation Time**: ~2 hours
- **Documentation Lines**: +498 (API_CALLCHAIN.md)
---
## Future Improvements
### 1. Type Definitions for Bottom-Layer API
**Current**: `callback: (data: any) => void`
**Future**:
```typescript
interface ComponentProperty {
name: string;
children: Array<{ name: string; value: string | number }>;
}
interface PropertyData {
properties: ComponentProperty[];
materials: any[];
}
callback: (data: PropertyData) => void
```
---
### 2. Loading State Optimization
**Current**: Simple "加载中..." text
**Future**:
- Skeleton loading UI
- Progress indicator
- Error handling with retry
---
### 3. "显示全部" Implementation
**Current**: `console.log('[EngineManager] 显示全部')`
**Future**: 调用底层 API 显示所有隐藏构件
---
### 4. Property Panel Cache
**Current**: 每次打开都重新请求
**Future**:
- Cache recently viewed properties
- Invalidate on model change
- Reduce API calls
---
## Success Criteria - All Met ✅
- [x] 点击构件后,控制台输出选中信息
- [x] 有选中构件时,右键显示"构件详情"+"显示全部"
- [x] 无选中构件时,右键只显示"显示全部"
- [x] 点击"构件详情"弹出属性弹窗
- [x] 弹窗正确展示底层 API 返回的属性数据
- [x] 点击"显示全部"控制台输出提示
- [x] 构建成功
- [x] 调用链文档已重构为全局文档
- [x] 新增右键菜单章节
- [x] 新增构件交互章节