UI and Plugin System
About 3 min
UI and Plugin System
Ribbon toolbar, built-in dialogs, context menus, and the plugin system.
If you need to create custom dialogs, see custom-dialog.md.
Ribbon Toolbar
Add a Custom Tab
const { RibbonConfigManager, CommandRegistry, CommandDefinition, CommandOptions } = vjcad;
// 1. Register the command first
class MyToolCommand {
async main() {
console.log('Run my tool');
}
}
const opt = new CommandOptions();
opt.useAutoComplete = true;
CommandRegistry.regist(new CommandDefinition('MYTOOL', 'My Tool', MyToolCommand, opt));
// 2. Create tab configuration
const newTab = {
id: 'my-tab',
label: 'Custom',
groups: [{
id: 'my-tools',
label: 'My Tools',
primaryButtons: [
{
cmd: 'MYTOOL',
icon: 'circle',
label: 'Create Circle',
tooltip: 'Create a circle'
}
],
moreButtons: [
{ cmd: 'ZOOM', icon: 'zoom', label: 'Zoom' }
]
}]
};
// 3. Add to config and refresh
const currentConfig = RibbonConfigManager.getConfig();
currentConfig.tabs.push(newTab);
RibbonConfigManager.refresh();Add to an Existing Tab
// Add a group to an existing tab
Engine.view.ribbonBar.addGroup('plugins', {
id: 'my-plugin-group',
label: 'My Plugin',
pinnable: true,
primaryButtons: [
{
icon: 'mygrid',
cmd: 'MYGRID',
prompt: 'Draw Grid',
type: 'large' // 'large' | 'small'
}
]
});
// Remove group
Engine.view.ribbonBar.removeGroup('plugins', 'my-plugin-group');Message Output
const { writeMessage, message } = vjcad;
// Output to command line (HTML supported)
writeMessage("Normal message");
writeMessage("<br/>Line break message");
writeMessage("<br/><span style='color:red'>Red warning</span>");
writeMessage("<br/><b>Bold message</b>");
// Info toast/console prompt
message.info("Information");Dialogs
Confirmation Dialogs
const { showConfirm, showWarningConfirm, showInfo, showError } = vjcad;
// Simple confirmation
const result = await showConfirm("Are you sure you want to delete?", "Confirm Delete");
// result: 'yes' | 'no'
// Warning confirmation
const result = await showWarningConfirm("This operation cannot be undone!", "Warning");
// Information prompt
await showInfo("Operation completed", "Info");
// Error prompt
await showError("Operation failed: file not found", "Error");Advanced Confirmation Dialog
const { YesNoDialog, YesNoDialogConfig } = vjcad;
const config = new YesNoDialogConfig({
title: "Save Changes",
message: "The file has been modified. Save changes?",
type: "warning", // 'info' | 'warning' | 'error' | 'confirm'
yesTitle: "Save(S)",
noTitle: "Don't Save(N)",
showCancel: true,
cancelTitle: "Cancel"
});
const dialog = new YesNoDialog();
const result = await dialog.showMessageBox(config);
// result: 'yes' | 'no' | 'cancel'Input Dialogs
const { showPrompt, showInputDialog, showSelectDialog } = vjcad;
// Simple input
const name = await showPrompt("Please enter a name:", "Default value", "Input");
// Returns a string or null
// Validated input
const result = await showInputDialog({
title: "Set Circle Radius",
label: "Enter radius value (1-100):",
placeholder: "Enter a number...",
defaultValue: "25",
required: true,
type: "text", // 'text' | 'password'
validator: (value) => {
const num = parseFloat(value);
if (isNaN(num)) return "Please enter a valid number";
if (num < 1 || num > 100) return "Radius must be between 1 and 100";
return null; // null means validation passed
},
description: "The entered radius will be used to create a new circle"
});
if (result.confirmed) {
const radius = parseFloat(result.value);
}
// Dropdown selection
const result = await showSelectDialog({
title: "Select Linetype",
label: "Please choose a linetype:",
options: [
{ value: "Continuous", label: "Continuous (solid)" },
{ value: "DASHED", label: "DASHED (dashed)" },
{ value: "CENTER", label: "CENTER (center line)" }
],
defaultValue: "Continuous",
description: "The selected linetype will be applied to newly created lines"
});Context Menu
Basic Usage
const { CadEventManager, CadEvents, Engine } = vjcad;
const events = CadEventManager.getInstance();
events.on(CadEvents.ContextMenuOpening, (args) => {
// Add before default menu
args.prependItems.push({
label: "My Tools",
icon: "tool",
submenu: [
{
label: "Draw Circle",
icon: "circle",
callback: () => Engine.editor.executerWithOp('CIRCLE')
},
{
label: "Draw Line",
icon: "line",
callback: () => Engine.editor.executerWithOp('LINE')
}
]
});
// Separator
args.prependItems.push({ isSeparator: true });
// Add after default menu
args.appendItems.push({
label: "Zoom Extents",
shortcut: "Z+E",
callback: () => Engine.zoomExtents()
});
});Customize by Command State
events.on(CadEvents.ContextMenuOpening, (args) => {
if (args.isCommandActive) {
// A command is running
if (args.activeCommandName === 'LINE') {
args.appendItems.push({
label: "Close",
callback: () => writeMessage("<br/>C")
});
}
} else {
// Idle state
args.prependItems.push({
label: "Quick Commands",
submenu: [
{ label: "Draw Line", command: "LINE" },
{ label: "Draw Circle", command: "CIRCLE" }
]
});
}
});Fully Custom Menu
events.on(CadEvents.ContextMenuOpening, (args) => {
args.useDefaultItems = false; // Disable default items
args.prependItems.push({
label: "Custom Menu",
callback: () => { /* ... */ }
});
});Disable Right-Click Menu
events.on(CadEvents.ContextMenuOpening, (args) => {
args.cancel = true;
});Menu Item Properties
| Property | Description |
|---|---|
label | Menu text |
icon | Icon name |
shortcut | Shortcut text |
callback | Click callback function |
command | Command name |
submenu | Submenu array |
isSeparator | Whether it is a separator |
disabled | Whether disabled |
Plugin System
Basic Plugin Structure
class MyPlugin {
constructor() {
this.name = "MyPlugin";
this.version = "1.0.0";
this.description = "Example plugin";
this.commands = [];
this.eventHandlers = [];
}
install() {
this.registerCommands();
this.addEventListeners();
this.initUI();
}
uninstall() {
// Remove event listeners
this.eventHandlers.forEach(handler => {
Engine.eventManager.off(handler.event, handler.callback);
});
// Unregister commands
this.commands.forEach(cmdName => {
CommandRegistry.unregist(cmdName);
});
// Remove UI
Engine.view.ribbonBar.removeGroup('plugins', 'my-plugin-group');
}
registerCommands() {
class DrawGridCommand {
async main() {
// Grid drawing logic
}
}
CommandRegistry.regist(
new CommandDefinition('MYGRID', 'Draw Grid', DrawGridCommand)
);
this.commands.push('MYGRID');
}
addEventListeners() {
const onEntityAdded = (args) => {
console.log('Entity added:', args.entity.type);
};
Engine.eventManager.on(CadEvents.EntityAdded, onEntityAdded);
this.eventHandlers.push({
event: CadEvents.EntityAdded,
callback: onEntityAdded
});
}
initUI() {
// Register icon
IconRegistry.registerCommandIcon('MYGRID', '<svg>...</svg>');
// Add Ribbon group
Engine.view.ribbonBar.addGroup('plugins', {
id: 'my-plugin-group',
label: 'My Plugin',
primaryButtons: [
{ icon: 'mygrid', cmd: 'MYGRID', prompt: 'Draw Grid', type: 'large' }
]
});
}
}
// Install plugin
const plugin = new MyPlugin();
plugin.install();Standard Plugin Structure (Plugin Interface)
export default {
manifest: {
id: 'my-demo-plugin',
name: 'Example Plugin',
version: '1.0.0',
author: 'vjmap.com',
description: 'Demonstrates standard plugin structure',
keywords: ['demo', 'grid']
},
onLoad(context) {
console.log('Plugin loaded');
},
onActivate(context) {
// Register icon
context.registerIcon('MYGRID', '<svg>...</svg>');
// Register command
context.registerCommand('MYGRID', 'Draw Grid', DrawGridCommand);
// Add menu item
context.addMenuItem('tool', { command: 'MYGRID' });
// Add Ribbon group
context.addRibbonGroup('plugins', {
id: 'my-demo',
label: 'Example Tools',
primaryButtons: [
{ icon: 'mygrid', cmd: 'MYGRID', prompt: 'Draw Grid', type: 'large' }
]
});
},
onDeactivate(context) {
console.log('Plugin deactivated');
},
onUnload(context) {
console.log('Plugin unloaded');
}
};PluginManager API
const { PluginManager } = vjcad;
const pm = PluginManager.getInstance();
// Load plugin
await pm.loadFromContent(jsContent, cssContent); // From code content
await pm.loadFromUrl(jsUrl, cssUrl); // From URL
await pm.loadFromPath(jsPath, cssPath); // From local path
// Plugin management
pm.getLoadedPlugins(); // Get loaded plugin list
pm.getPluginInfo(id); // Get info for specified plugin
pm.isLoaded(id); // Check whether loaded
pm.isActive(id); // Check whether active
pm.activate(id); // Activate plugin
pm.deactivate(id); // Deactivate plugin
pm.unload(id); // Unload pluginPlugin Lifecycle
onLoad- called when the plugin is loaded (only once)onActivate- called when the plugin is activated (can be called multiple times)onDeactivate- called when the plugin is deactivated (can be called multiple times)onUnload- called when the plugin is unloaded (only once)