侧边栏面板
文档:
vjcad/doc/ui/sidebar-panel.md示例:
vjcad/example/ui/14sidebarpanel.js— 自定义面板vjcad/example/ui/16menucustom.js—leftPanels / rightPanels配置
1. 整体模型
vjcad 在主区域两侧各有一条 ActivityBar(图标栏),点击图标即激活右侧 / 左侧的 SidebarPanel。每个 SidebarPanel 是一个 Web Component,由 SidebarPanelManager 管理生命周期。
[ActivityBar 图标] → 激活 SidebarPanel → 渲染你写的 Web Component
侧边栏整体显示由 MainView({ sidebarStyle }) 控制:
sidebarStyle |
含义 |
|---|---|
'right'(默认) |
仅右侧 |
'left' |
仅左侧 |
'both' |
左右都显示 |
'none' |
不显示侧边栏(仍可由命令打开) |
2. 注册自己的面板
import { BasePanelComponent, registerSidebarPanel, activateSidebarPanel,
IconRegistry, IconCategory, Engine, html, css } from 'vjcad'; // html/css/LitElement 直接从 'vjcad' 导出,没有 'vjcad/lit' 子路径
// 1. 写一个继承 BasePanelComponent 的 Web Component
class MyToolPanel extends BasePanelComponent {
static styles = css`
.toolbar { padding: 10px; }
.btn { display: block; width: 100%; margin: 5px 0; padding: 8px;
background: var(--theme-color-button-bg, #2d3748); color: var(--theme-color-font, #e8eaed);
border: 1px solid var(--theme-color-border, #3d4a5c); cursor: pointer; }
.btn:hover { background: var(--theme-color-button-hover-bg, #3d4a5c); }
`;
render() {
return html`
<div class="toolbar">
<button class="btn" @click=${this._drawLine}>画直线</button>
<button class="btn" @click=${this._drawCircle}>画圆</button>
<button class="btn" @click=${() => Engine.zoomExtents()}>缩放全图</button>
</div>`;
}
_drawLine() { Engine.editor.executerWithOp('LINE'); }
_drawCircle() { Engine.editor.executerWithOp('CIRCLE'); }
// ⚠️ BasePanelComponent 上的 onActivate / onDeactivate / refresh 只是空钩子,框架从不调用;
// 侧栏只把"当前面板"渲染进内容区,切换时面板元素被挂上 / 摘下 → 用 DOM 生命周期感知
connectedCallback() { super.connectedCallback(); /* 被切到(显示) */ }
disconnectedCallback() { super.disconnectedCallback(); /* 被切走(隐藏) */ }
}
customElements.define('my-tool-panel', MyToolPanel);
// 2. 图标:icon 不是 URL,而是 images/<分类>/<名>.svg 形式的注册表键;先把 SVG 注册进 Actbar 分类
IconRegistry.registerIcon('my-tools', '<svg viewBox="0 0 24 24">...</svg>', { category: IconCategory.Actbar });
// 3. 注册到 ActivityBar(注册时立即创建面板实例)
registerSidebarPanel({
name: 'my-tools', // 唯一标识(必须)
label: '我的工具', // 面板标题(必须)
icon: 'images/actbar/my-tools.svg', // 解析为 Actbar 分类的 'my-tools' 图标
position: 'left', // 可选 'left' | 'right',默认 'left'
panelClass: MyToolPanel, // LitElement 子类(必须)
tagName: 'my-tool-panel', // 上面已 define 过 → 必须传同一个 tagName
});
// 4. 立即激活
activateSidebarPanel('my-tools');
SidebarPanelConfig 字段(d.ts 完整定义,就这 7 个):
| 字段 | 必填 | 说明 |
|---|---|---|
name |
√ | 唯一 ID |
label |
√ | 显示名 |
icon |
√ | 不是 URL:须写成 images/<分类>/<名>.svg(通常 images/actbar/<名>.svg),按分类 + 名字去 IconRegistry 查;不会去加载文件,查不到就不显示图标 |
position |
'left' / 'right',默认 'left' |
|
panelClass |
√ | LitElement 子类(不一定是 BasePanelComponent) |
tagName |
自定义元素标签(必须含 -;缺省自动生成 sidebar-panel-{name} 并 define)。同一个类只能 define 到一个标签:若你已 customElements.define 过该类,必须传同一个 tagName,否则自动 define 抛错 → registerPanel 返回 false |
|
order |
⚠️ 字段存在但不生效:排序读的是面板实例上的 order(从未赋值),实际按注册顺序排列 |
⚠️
activatedByCommand/defaultActive/minWidth/tooltip/data等字段不在SidebarPanelConfig声明里, 运行时registerPanel也不会消费它们——不要依赖。
3. BasePanelComponent 生命周期
class BasePanelComponent extends LitElement {
panelId: number; // 管理器分配的 id
name: string; // = config.name
caption: string; // = config.label
label: string; // = config.label
src: string; // = config.icon
isActive: boolean; // 是否当前激活(普通字段,由侧栏直接赋值)
onActivate(): void; // ⚠️ 空钩子,框架从不调用
onDeactivate(): void; // ⚠️ 空钩子,框架从不调用
refresh(): void; // 默认 requestUpdate();框架不会自动调,需要时自己调
}
⚠️ 组件实例上没有
.config成员(管理器只注入上面几个字段)。 想给面板传自定义数据,请在类里写死或通过模块作用域传入。
激活流程(实际行为):
registerSidebarPanel时就document.createElement(tagName)立即实例化,并注入name / label / caption / src / panelId;视图已就绪则重建侧边栏- 用户点击图标 /
activateSidebarPanel(name)→ 侧边栏只把各面板isActive置 false、目标面板置 true 并重绘 onActivate()/onDeactivate()/refresh()从不被框架调用(基类里只是空方法 /requestUpdate());侧栏内容区只渲染当前面板,切换时面板元素会被挂上 / 摘下,可用connectedCallback / disconnectedCallback(记得调super)感知;数据变了自己调refresh()- 注销(
unregisterSidebarPanel):实例被移除(组件自身没有dispose回调);插件经context.registerSidebarPanel注册的面板在插件unload时移除
4. 实用 API
import {
registerSidebarPanel, unregisterSidebarPanel,
activateSidebarPanel,
SidebarPanelManager
} from 'vjcad';
// 注意:没有 deactivateSidebarPanel / getActiveSidebarPanel 导出
registerSidebarPanel(config);
unregisterSidebarPanel('my-tools');
activateSidebarPanel('my-tools'); // = 用户点图标
// 直接拿管理器(高级)——⚠️ d.ts 注释声称的另一入口
// `Engine.sidebarPanelManager` 运行时是 undefined,只能走 getInstance()
const mgr = SidebarPanelManager.getInstance();
mgr.getRegisteredPanels(); // 无参,返回全部已注册 name[]
mgr.getPanelInstance('my-tools'); // 拿到实例(不是 getPanelComponent)
mgr.getLeftPanels(); mgr.getRightPanels();// 分左右列出
mgr.isPanelRegistered('my-tools');
mgr.unregisterPanelsByOwner('my-plugin'); // 插件卸载批量清理
## 5. 通过 URL 激活
启动时可用 `vcad_panel` query 参数激活指定面板(`MainView.URL_PARAMS.PANEL`):
?vcad_panel=my-tools
适合分享 / 嵌入场景。
## 6. MainView 配置内置面板
`leftPanels` / `rightPanels` 是**内置面板名的字符串数组**(不是对象配置):
```js
new MainView({
leftPanels: ['props', 'dsettings'], // 左侧只留这两个内置面板
rightPanels: ['blocks', 'images', 'commands', 'alias', 'history'],
autoActivatePropertyPanel: true // 选中实体自动弹属性面板
});
可用内置面板名:
props(属性)、dsettings(绘图设置)、blocks(块)、images(图像)、commands(命令)、alias(别名)、history(历史)等,详见vjcad/doc/ui/sidebar-panel.md。 自定义面板请走registerSidebarPanel,不能写进这两个数组。
7. 常见样式钩子(CSS 变量)
vjcad 没有 --button-bg / --text-color / --section-bg / --side-panel-bg-color / --side-panel-border-color 这类变量。想让面板跟随主题,在自己的 static styles 里直接用真实的全局主题变量(setThemeColors 写在 document.documentElement 上,可穿透 shadow DOM 继承):
| 变量 | 用途(ThemeColors 键) |
|---|---|
--theme-color-sidebarframe-contents |
侧栏内容底色(sidebarContentsBg) |
--theme-color-font / --theme-color-font-secondary |
主 / 次文字色(font / fontSecondary) |
--theme-color-border |
边框(border;别名 --border-color) |
--theme-color-button-bg / --theme-color-button-hover-bg |
按钮底色 / 悬停(buttonBg / buttonHoverBg) |
--theme-color-input-bg |
输入框底色(inputBg) |
--theme-color-accent / --theme-hover |
强调色 / 悬停高亮(accent / hover) |
完整清单见导出常量 THEME_COLOR_CSS_VARS。自己起名的变量(如 --my-btn-bg)当然也能用,但它们只是你自己的约定,vjcad 不会设置。
红旗
customElements.define('my-tool-panel', ...)的 tagName 必须包含-,否则浏览器拒绝- 同一个
panelClass想挂多个name:必须每次都传同一个已 define 的tagName(一个类只能对应一个标签名),之后每个 name 各有一个实例;内部状态请存在实例字段,别写到模块级全局 icon不是 URL,必须是images/<分类>/<名>.svg且该名字已在对应分类注册(自定义图标先IconRegistry.registerIcon(name, svg, { category: IconCategory.Actbar })),否则 ActivityBar 上看不见图标(看不到点不开就没法激活)order不生效,面板按注册顺序排列leftPanels/rightPanels数组里写的是内置面板名(如'props'),不是中文标签,自定义面板也放不进去autoActivatePropertyPanel: true配合sidebarStyle: 'none'时无效(侧边栏被整体关闭了)
参考(官方文档 MCP)
- 文档:
vjcad/doc/ui/sidebar-panel.md - 示例:
vjcad/example/ui/14sidebarpanel.js— 自定义面板vjcad/example/ui/16menucustom.js—leftPanels / rightPanels配置