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

12 KiB
Raw Permalink Blame History

Learnings: 构件详情右键菜单功能

2026-01-28 - Implementation Complete

Architecture Patterns

1. Event-Driven Component Selection

Pattern: 底层引擎触发事件 → SDK 监听并维护状态

// 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: 注册处理器返回函数,运行时动态生成菜单

// 注册时传入函数,而非静态配置
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: 单例注册表 + 代理方法

// 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 展示

// 底层返回格式
{
  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

// 中文日志 + Manager 前缀
console.log('[Engine] 构件选中:', this.selectedComponent);
console.log('[ComponentDetailManager] 显示构件详情');
console.error('[EngineManager] Engine 尚未初始化');

Reason:

  • 中文便于业务人员理解
  • 前缀便于定位代码位置
  • 区分 log/warn/error 级别

2. Section Markers

// ==================== 选中状态管理 ====================

private selectedComponent: { url: string; id: string } | null = null;

public getSelectedComponent(): { url: string; id: string } | null {
    return this.selectedComponent;
}

Reason: 大文件中用分隔符标记功能区域,提升可读性。


3. JSDoc Comments

/**
 * 获取当前选中的构件信息
 * @returns 选中的构件信息,包含 url 和 id未选中时返回 null
 */
public getSelectedComponent(): { url: string; id: string } | null {
    return this.selectedComponent;
}

Reason:

  • 只对 public 方法添加 JSDoc
  • 描述功能、参数、返回值
  • 符合现有代码风格

TypeScript Patterns

1. Optional Chaining + Nullish Coalescing

// 安全访问 + 默认值
return this.engineInstance?.getSelectedComponent() ?? null;

// 条件调用
registry.componentDetail?.show(url, id);

Reason:

  • Manager 可能未初始化
  • 优雅处理 null/undefined

2. Type-Safe Event Callbacks

// 明确回调参数类型
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

// 按功能分组
menu: {
    componentDetail: '构件详情',
    showAll: '显示全部'
}

panel: {
    componentDetail: {
        title: '构件详情'
    }
}

Reason:

  • 层级结构便于管理
  • 避免命名冲突
  • 易于扩展

2. Type-Safe Translation

// 先定义类型
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 访问
// 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 负责展示
const categories = data.properties.map(cat => ({
    categoryName: cat.name,
    items: cat.children.map(child => ({
        key: child.name,
        value: child.value
    }))
}));

Build & Verification

Build Command

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:

  • TypeScript 编译无错误
  • 项目级 LSP diagnostics 无错误
  • 模块打包成功ESM + UMD
  • 文件大小在合理范围

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:

// 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:

// 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:

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

// ❌ 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 一次

// ✅ 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:

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

  • 点击构件后,控制台输出选中信息
  • 有选中构件时,右键显示"构件详情"+"显示全部"
  • 无选中构件时,右键只显示"显示全部"
  • 点击"构件详情"弹出属性弹窗
  • 弹窗正确展示底层 API 返回的属性数据
  • 点击"显示全部"控制台输出提示
  • 构建成功
  • 调用链文档已重构为全局文档
  • 新增右键菜单章节
  • 新增构件交互章节