Sidebar Panels
Sidebar Panels
Use SidebarPanelManager and BasePanelComponent to create custom sidebar panels and integrate them seamlessly with built-in system panels.
Online Example
| Example | Description | Link |
|---|---|---|
| Sidebar Panel | Custom sidebar panel with registerSidebarPanel | Online Demo{target="_blank"} |
Overview
Sidebar panels are important UI components in WebCAD for displaying tools, properties, information, and more. Through the SidebarPanelManager API, developers can:
- Register custom panels in the left or right sidebar
- Create rich interactive panel interfaces
- Integrate seamlessly with built-in system panels
Core API
Interface Definition
/**
* Sidebar panel configuration interface
*/
interface SidebarPanelConfig {
/** Unique panel identifier */
name: string;
/** Display label */
label: string;
/** Panel icon path */
icon: string;
/** Panel position: "left" | "right" (default "left") */
position?: "left" | "right";
/** Panel component class (extends BasePanelComponent) */
panelClass: typeof BasePanelComponent;
/** Custom element tag name (optional) */
tagName?: string;
/** Sort order (smaller means earlier, default 100) */
order?: number;
}Convenience Functions
// Register sidebar panel
registerSidebarPanel(config: SidebarPanelConfig): boolean
// Unregister sidebar panel
unregisterSidebarPanel(name: string): boolean
// Activate specified panel
activateSidebarPanel(name: string): booleanSidebarPanelManager Class
// Get manager instance
const manager = SidebarPanelManager.getInstance();
// Register panel
manager.registerPanel(config);
// Unregister panel
manager.unregisterPanel(name);
// Activate panel
manager.activatePanel(name);
// Get registered panels
manager.getRegisteredPanels();
// Check if panel is registered
manager.isPanelRegistered(name);
// Get panel instance
manager.getPanelInstance(name);Usage Steps
1. Create Panel Class
Extend BasePanelComponent to create a custom panel class:
const { BasePanelComponent, html, css } = vjcad;
class MyToolPanel extends BasePanelComponent {
// Merge base styles (recommended)
static styles = [
BasePanelComponent.baseStyles,
css`
.my-custom-style {
color: #58a6ff;
}
`
];
// Define reactive properties
static properties = {
counter: { type: Number }
};
constructor() {
super();
this.counter = 0;
}
// Render panel content
render() {
return html`
<div class="panel-content">
<div class="panel-section">
<div class="panel-section-title">My Tools</div>
<div class="panel-row">
<span class="panel-label">Counter:</span>
<span class="panel-value">${this.counter}</span>
</div>
<button class="panel-btn panel-btn-primary"
@click=${() => this.counter++}>
Increase
</button>
</div>
</div>
`;
}
}2. Register Panel
const { registerSidebarPanel } = vjcad;
registerSidebarPanel({
name: "my-tools",
label: "My Tools",
icon: "./images/actbar/tools.svg",
position: "left",
panelClass: MyToolPanel,
order: 10
});3. Activate Panel
const { activateSidebarPanel } = vjcad;
// Switch to custom panel
activateSidebarPanel("my-tools");4. Activate Panel via URL Parameter
You can use the URL parameter vcad_panel to automatically activate a panel after the page loads, without manually calling activateSidebarPanel in code:
// Automatically open custom panel after page load
https://your-domain.com/?vcad_panel=my-tools
// Use together with drawing parameters
https://your-domain.com/?vcad_mapid=building_plan&vcad_panel=ai-assistantTips
The value of vcad_panel is exactly the name configured when the panel is registered. The panel must already be registered during plugin initialization for URL activation to work.
Built-in Styles of BasePanelComponent
BasePanelComponent.baseStyles provides a rich set of predefined style classes:
Layout Classes
| Class | Description |
|---|---|
.panel-content | Panel content container with padding and scrollbar |
.panel-section | Section container |
.panel-section-title | Section title with underline |
.panel-row | Row layout with flex alignment |
Form Classes
| Class | Description |
|---|---|
.panel-label | Label text, gray |
.panel-value | Value text, white |
.panel-input | Input style with dark background |
Button Classes
| Class | Description |
|---|---|
.panel-btn | Base button style |
.panel-btn-primary | Primary button (blue) |
List Classes
| Class | Description |
|---|---|
.panel-list | List container |
.panel-list-item | List item with hover effect |
.panel-list-item.active | Active list item |
Lifecycle Methods
BasePanelComponent provides the following overridable lifecycle methods:
class MyPanel extends BasePanelComponent {
// Called when panel is activated
onActivate() {
console.log("Panel activated");
this.loadData();
}
// Called when panel is deactivated
onDeactivate() {
console.log("Panel deactivated");
}
// Refresh panel content
refresh() {
this.requestUpdate();
}
}Complete Examples
Drawing Tools Panel
const {
BasePanelComponent, html, css,
registerSidebarPanel,
Engine, LineEnt, CircleEnt,
writeMessage
} = vjcad;
class DrawToolsPanel extends BasePanelComponent {
static styles = [
BasePanelComponent.baseStyles,
css`
.tool-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.color-swatch {
width: 24px;
height: 24px;
border-radius: 4px;
cursor: pointer;
border: 2px solid transparent;
}
.color-swatch.active {
border-color: #fff;
}
`
];
static properties = {
currentColor: { type: Number },
entityCount: { type: Number }
};
constructor() {
super();
this.currentColor = 1;
this.entityCount = 0;
}
connectedCallback() {
super.connectedCallback();
this.updateStats();
}
updateStats() {
this.entityCount = (Engine.currentSpace?.items || []).length;
}
drawLine() {
const x1 = Math.random() * 100;
const y1 = Math.random() * 100;
const line = new LineEnt(
[x1, y1],
[x1 + 50, y1 + 30]
);
line.setDefaults();
line.color = this.currentColor;
Engine.addEntities(line);
Engine.zoomExtents();
this.updateStats();
writeMessage("<br/>Line created");
}
drawCircle() {
const cx = Math.random() * 100;
const cy = Math.random() * 100;
const circle = new CircleEnt([cx, cy], 20);
circle.setDefaults();
circle.color = this.currentColor;
Engine.addEntities(circle);
Engine.zoomExtents();
this.updateStats();
writeMessage("<br/>Circle created");
}
setColor(color) {
this.currentColor = color;
}
render() {
const colors = [1, 2, 3, 4, 5, 6, 7];
return html`
<div class="panel-content">
<div class="panel-section">
<div class="panel-section-title">Drawing Tools</div>
<div class="tool-grid">
<button class="panel-btn" @click=${this.drawLine}>
Draw Line
</button>
<button class="panel-btn" @click=${this.drawCircle}>
Draw Circle
</button>
</div>
</div>
<div class="panel-section">
<div class="panel-section-title">Color</div>
<div style="display: flex; gap: 4px;">
${colors.map(c => html`
<div class="color-swatch ${this.currentColor === c ? 'active' : ''}"
style="background: ${this.getColorHex(c)}"
@click=${() => this.setColor(c)}>
</div>
`)}
</div>
</div>
<div class="panel-section">
<div class="panel-section-title">Statistics</div>
<div class="panel-row">
<span class="panel-label">Entity Count:</span>
<span class="panel-value">${this.entityCount}</span>
</div>
</div>
</div>
`;
}
getColorHex(index) {
const map = {
1: '#ff0000', 2: '#ffff00', 3: '#00ff00',
4: '#00ffff', 5: '#0000ff', 6: '#ff00ff', 7: '#ffffff'
};
return map[index] || '#ffffff';
}
}
// Register panel
registerSidebarPanel({
name: "draw-tools",
label: "Drawing Tools",
icon: "./images/actbar/actbar-draw-settings.svg",
position: "left",
panelClass: DrawToolsPanel,
order: 5
});Entity Info Panel
class EntityInfoPanel extends BasePanelComponent {
static styles = [
BasePanelComponent.baseStyles,
css`
.info-card {
background: #1a242e;
border-radius: 6px;
padding: 12px;
margin-bottom: 8px;
}
.entity-type {
color: #58a6ff;
font-weight: 600;
}
`
];
static properties = {
selectedEntities: { type: Array }
};
constructor() {
super();
this.selectedEntities = [];
}
refresh() {
const { ssGetFirst } = vjcad;
const ss = ssGetFirst();
this.selectedEntities = ss || [];
this.requestUpdate();
}
getTypeName(entity) {
const map = {
'LINE': 'Line', 'CIRCLE': 'Circle',
'ARC': 'Arc', 'TEXT': 'Text'
};
return map[entity.dxfName] || entity.dxfName;
}
render() {
return html`
<div class="panel-content">
<div class="panel-section">
<div class="panel-section-title">Selection Info</div>
<div class="panel-row">
<span class="panel-label">Selected Count:</span>
<span class="panel-value">${this.selectedEntities.length}</span>
</div>
<button class="panel-btn panel-btn-primary"
style="width: 100%; margin-top: 8px;"
@click=${this.refresh}>
Refresh
</button>
</div>
${this.selectedEntities.length > 0 ? html`
<div class="panel-section">
<div class="panel-section-title">Entity Details</div>
${this.selectedEntities.slice(0, 5).map(ent => html`
<div class="info-card">
<div class="entity-type">${this.getTypeName(ent)}</div>
<div class="panel-row">
<span class="panel-label">Layer:</span>
<span class="panel-value">${ent.layer || '0'}</span>
</div>
<div class="panel-row">
<span class="panel-label">Color:</span>
<span class="panel-value">${ent.color ?? 'ByLayer'}</span>
</div>
</div>
`)}
</div>
` : html`
<div style="text-align: center; color: #8b949e; padding: 40px;">
Please select entities and click Refresh
</div>
`}
</div>
`;
}
}
// Register on the right side
registerSidebarPanel({
name: "entity-info",
label: "Entity Info",
icon: "./images/actbar/actbar-property.svg",
position: "right",
panelClass: EntityInfoPanel
});Disable Auto Switch to the Properties Panel
By default, selecting entities automatically activates the properties panel. If you have a custom panel and do not want selection to switch away from the current panel automatically, disable this behavior in MainView config:
const cadView = new MainView({
// ... other config
autoActivatePropertyPanel: false // Do not auto-switch to properties panel when selecting entities
});Configure Built-in Panel Positions
By default, the system includes 6 built-in panels:
- Left: properties (
props), blocks (blocks), images (images) - Right: snap settings (
dsettings), command list (commands), command aliases (alias)
You can adjust their left/right positions through MainView config:
const cadView = new MainView({
// ... other config
// Configure left panels
leftPanels: ["props", "dsettings"], // Left: properties, snap settings
// Configure right panels
rightPanels: ["blocks", "images", "commands", "alias"] // Right: blocks, images, commands, aliases
});Available Panel Names
| Name | Description |
|---|---|
props | Properties panel - shows properties of selected entities |
blocks | Blocks panel - manage blocks |
images | Images panel - manage images |
dsettings | Snap settings panel - object snap configuration |
commands | Command list panel - list available commands |
alias | Command alias panel - command shortcut settings |
Notes
- Each panel can appear on only one side; do not configure the same panel in both
leftPanelsandrightPanels - Panels not listed in the config will not be shown
- The config order determines the display order in the activity bar
Notes
- Custom element naming:
tagNamemust include a hyphen, such asmy-panel, which is required by the Web Components spec - Style isolation: panels use Shadow DOM, so styles are isolated automatically and will not affect other components
- Icon paths:
iconsupports relative paths and absolute URLs; system icon path format is recommended - Lifecycle: use LitElement lifecycle methods such as
connectedCallbackfor initialization - Reactive properties: use
static propertiesto define state that requires reactive updates - Panel switching: if
autoActivatePropertyPanel: falseis set, selecting entities will not automatically switch panels, and the user must switch panels manually in the activity bar