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.
This commit is contained in:
541
.sisyphus/notepads/component-detail-rightclick/learnings.md
Normal file
541
.sisyphus/notepads/component-detail-rightclick/learnings.md
Normal file
@@ -0,0 +1,541 @@
|
||||
# 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] 新增构件交互章节
|
||||
Reference in New Issue
Block a user