mcpField
E
electrobun-window-management

electrobun-window-management

github.com/rajavijayachDevelopment1194 installsUpdated 36w ago
Repository

About electrobun-window-management

A skill for developers building applications with Electrobun. It presents patterns for creating, organizing, and coordinating windows and embedded BrowserView instances, including lifecycle and communication examples.

Key features

  1. 01Multi-window management
  2. 02BrowserView embedding and tabs
  3. 03Window state persistence
  4. 04Parent-child window groups
  5. 05Cross-window messaging and broadcasts
  6. 06Floating, modal, and positioned windows

Use cases

  1. 01Organize main, settings, and editor windows
  2. 02Build tab interfaces with BrowserView
  3. 03Restore saved window geometry
  4. 04Coordinate messages across windows

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/rajavijayach/electrobun-skills --skill electrobun-window-management

After installing, type/electrobun-window-managementin Claude Code to confirm it loaded.

Details

From the project README

Electrobun Window Management

Advanced patterns for managing windows and views in Electrobun applications.

Multi-Window Applications

Basic Window Manager
import { BrowserWindow } from "electrobun/bun";

class WindowManager {
  private windows = new Map<string, BrowserWindow>();
  
  createWindow(id: string, options: any) {
    const win = new BrowserWindow(options);
    this.windows.set(id, win);
    
    win.on("close", () => {
      this.windows.delete(id);
    });
    
    return win;
  }
  
  getWindow(id: string) {
    return this.windows.get(id);
  }
  
  getAllWindows() {
    return Array.from(this.windows.values());
  }
  
  closeWindow(id: string) {
    const win = this.windows.get(id);
    if (win) {
      win.close();
    }
  }
  
  closeAllWindows() {
    this.windows.forEach(win => win.close());
    this.windows.clear();
  }
}

const windowManager = new WindowManager();
Window Factory Pattern
type WindowType = "main" | "settings" | "editor" | "preview";

interface WindowConfig {
  type: WindowType;
  url: string;
  width: number;
  height: number;
  frame?: boolean;
  parent?: BrowserWindow;
}

class WindowFactory {
  private configs: Record<WindowType, Partial<WindowConfig>> = {
    main: {
      url: "views://main/index.html",
      width: 1200,
      height: 800,
      frame: true,
    },
    settings: {
      url: "views://settings/index.html",
      width: 600,
      height: 500,
      frame: true,
    },
    editor: {
      url: "views://editor/index.html",
      width: 1000,
      height: 700,
    },
    preview: {
      url: "views://preview/index.html",
      width: 800,
      height: 600,
      frame: false,
    }
  };
  
  create(type: WindowType, overrides?: Partial<WindowConfig>) {
    const config = { ...this.configs[type], ...overrides };
    return new BrowserWindow(config);
  }
}

const factory = new WindowFactory();
const mainWindow = factory.create("main");
const settingsWindow = factory.create("settings", { width: 700 });

BrowserView

BrowserView allows embedding webviews within a window without creating a separate window.

Basic BrowserView
import { BrowserWindow, BrowserView } from "electrobun/bun";

const win = new BrowserWindow({
  title: "Browser App",
  width: 1200,
  height: 800,
});

const view = new BrowserView({
  bounds: { x: 0, y: 60, width: 1200, height: 740 }
});

win.addBrowserView(view);
view.loadURL("https://example.com");

// Update bounds on window resize
win.on("resize", ({ width, height }) => {
  view.setBounds({ x: 0, y: 60, width, height: height - 60 });
});
Tab System with BrowserView
class TabManager {
  private tabs: Map<string, BrowserView> = new Map();
  private activeTab: string | null = null;
  private window: BrowserWindow;
  
  constructor(window: BrowserWindow) {
    this.window = window;
  }
  
  createTab(id: string, url: string) {
    const view = new BrowserView({
      bounds: this.getViewBounds()
    });
    
    view.loadURL(url);
    this.tabs.set(id, view);
    
    if (!this.activeTab) {
      this.activateTab(id);
    }
    
    return view;
  }
  
  activateTab(id: string) {
    const view = this.tabs.get(id);
    if (!view) return;
    
    // Hide current tab
    if (this.activeTab) {
      const currentView = this.tabs.get(this.activeTab);
      if (currentView) {
        this.window.removeBrowserView(currentView);
      }
    }
    
    // Show new tab
    this.window.addBrowserView(view);
    this.activeTab = id;
  }
  
  closeTab(id: string) {
    const view = this.tabs.get(id);
    if (!view) return;
    
    if (this.activeTab === id) {
      this.window.removeBrowserView(view);
      this.activeTab = null;
      
      // Activate another tab
      const otherTab = Array.from(this.tabs.keys()).find(k => k !== id);
      if (otherTab) {
        this.activateTab(otherTab);
      }
    }
    
    this.tabs.delete(id);
  }
  
  private getViewBounds() {
    const { width, height } = this.window.getBounds();
    return { x: 0, y: 60, width, height: height - 60 };
  }
  
  updateBounds() {
    const bounds = this.getViewBounds();
    this.tabs.forEach(view => {
      view.setBounds(bounds);
    });
  }
}

// Usage
const tabManager = new TabManager(mainWindow);

tabManager.createTab("tab1", "https://example.com");
tabManager.createTab("tab2", "https://github.com");
tabManager.activateTab("tab2");

mainWindow.on("resize", () => {
  tabManager.updateBounds();
});

Window Lifecycle Management

Window State Persistence
import { paths } from "electrobun/bun";
import { join } from "path";

interface WindowState {
  x: number;
  y: number;
  width: number;
  height: number;
  maximized: boolean;
}

class WindowStateManager {
  private stateFile: string;
  
  constructor(windowId: string) {
    this.stateFile = join(paths.userData, `window-state-${windowId}.json`);
  }
  
  async save(state: WindowState) {
    await Bun.write(this.stateFile, JSON.stringify(state, null, 2));
  }
  
  async load(): Promise<WindowState | null> {
    try {
      const content = await Bun.file(this.stateFile).text();
      return JSON.parse(content);
    } catch {
      return null;
    }
  }
  
  async restore(window: BrowserWindow) {
    const state = await this.load();
    if (!state) return false;
    
    window.move({ x: state.x, y: state.y });
    window.resize({ width: state.width, height: state.height });
    
    if (state.maximized) {
      window.maximize();
    }
    
    return true;
  }
  
  startTracking(window: BrowserWindow) {
    let saveTimeout: Timer;
    
    const saveState = () => {
      clearTimeout(saveTimeout);
      saveTimeout = setTimeout(async () => {
        const bounds = window.getBounds();
        await this.save({
          x: bounds.x,
          y: bounds.y,
          width: bounds.width,
          height: bounds.height,
          maximized: window.isMaximized(),
        });
      }, 500);
    };
    
    window.on("move", saveState);
    window.on("resize", saveState);
  }
}

// Usage
const stateManager = new WindowStateManager("main");
const win = new BrowserWindow({ width: 1200, height: 800 });

await stateManager.restore(win);
stateManager.startTracking(win);
Window Groups & Parent-Child
class WindowGroup {
  private parent: BrowserWindow;
  private children: BrowserWindow[] = [];
  
  constructor(parent: BrowserWindow) {
    this.parent = parent;
    
    // Close children when parent closes
    parent.on("close", () => {
      this.closeAllChildren();
    });
  }
  
  addChild(options: any) {
    const child = new BrowserWindow({
      ...options,
      parent: this.parent,
    });
    
    this.children.push(child);
    
    child.on("close", () => {
      const index = this.children.indexOf(child);
      if (index > -1) {
        this.children.splice(index, 1);
      }
    });
    
    return child;
  }
  
  closeAllChildren() {
    this.children.forEach(child => child.close());
    this.children = [];
  }
  
  showAll() {
    this.parent.show();
    this.children.forEach(child => child.show());
  }
  
  hideAll() {
    this.parent.hide();
    this.children.forEach(child => child.hide());
  }
}

// Usage
const mainWindow = new BrowserWindow({ width: 1200, height: 800 });
const group = new WindowGroup(mainWindow);

const palette = group.addChild({
  url: "views://palette/index.html",
  width: 250,
  height: 600,
  frame: false,
});

const inspector = group.addChild({
  url: "views://inspector/index.html",
  width: 300,
  height: 400,
});

Window Orchestration

Broadcasting to All Windows
class WindowBroadcaster {
  private windows: Set<BrowserWindow> = new Set();
  
  register(window: BrowserWindow) {
    this.windows.add(window);
    
    window.on("close", () => {
      this.windows.delete(window);
    });
  }
  
  async broadcast(method: string, ...args: any[]) {
    const promises = Array.from(this.windows).map(win =>
      win.rpc[method](...args).catch(err => {
        console.error(`Broadcast to window failed:`, err);
      })
    );
    
    await Promise.allSettled(promises);
  }
}

const broadcaster = new WindowBroadcaster();

// Register windows
broadcaster.register(mainWindow);
broadcaster.register(settingsWindow);

// Broadcast to all
await broadcaster.broadcast("updateTheme", { theme: "dark" });
Window Communication Hub
class WindowHub {
  private windows = new Map<string, BrowserWindow>();
  
  register(id: string, window: BrowserWindow) {
    this.windows.set(id, window);
    
    // Setup RPC handler for messaging
    window.defineRpc({
      handlers: {
        sendToWindow: async (targetId: string, method: string, ...args: any[]) => {
          return this.send(targetId, method, ...args);
        }
      }
    });
  }
  
  async send(targetId: string, method: string, ...args: any[]) {
    const target = this.windows.get(targetId);
    if (!target) {
      throw new Error(`Window ${targetId} not found`);
    }
    
    return await target.rpc[method](...args);
  }
  
  async broadcast(method: string, ...args: any[]) {
    const results = new Map();
    
    for (const [id, window] of this.windows) {
      try {
        const result = await window.rpc[method](...args);
        results.set(id, result);
      } catch (err) {
        results.set(id, { error: err.message });
      }
    }
    
    return results;
  }
}

const hub = new WindowHub();
hub.register("main", mainWindow);
hub.register("editor", editorWindow);

// Send from main to editor
await hub.send("editor", "updateContent", { text: "Hello" });

// Broadcast to all
await hub.broadcast("refreshData");

Floating Windows & Overlays

Always-on-Top Window
const floatingWindow = new BrowserWindow({
  url: "views://floating/index.html",
  width: 300,
  height: 200,
  frame: false,
  alwaysOnTop: true,
  skipTaskbar: true,
});

// Toggle always-on-top
floatingWindow.setAlwaysOnTop(!floatingWindow.isAlwaysOnTop());
Picture-in-Picture Mode
class PictureInPicture {
  private pipWindow: BrowserWindow | null = null;
  private sourceWindow: BrowserWindow;
  
  constructor(sourceWindow: BrowserWindow) {
    this.sourceWindow = sourceWindow;
  }
  
  enter(url: string) {
    if (this.pipWindow) return;
    
    this.pipWindow = new BrowserWindow({
      url,
      width: 400,
      height: 300,
      frame: false,
      alwaysOnTop: true,
      resizable: true,
    });
    
    this.pipWindow.on("close", () => {
      this.pipWindow = null;
    });
  }
  
  exit() {
    if (this.pipWindow) {
      this.pipWindow.close();
      this.pipWindow = null;
    }
  }
  
  toggle(url: string) {
    if (this.pipWindow) {
      this.exit();
    } else {
      this.enter(url);
    }
  }
}

const pip = new PictureInPicture(mainWindow);
pip.enter("views://video-player/index.html");

Advanced Patterns

Modal Dialogs
function showModal(parent: BrowserWindow, options: any) {
  const modal = new BrowserWindow({
    ...options,
    parent,
    modal: true,
    frame: false,
  });
  
  // Disable parent interaction
  parent.setEnabled(false);
  
  modal.on("close", () => {
    parent.setEnabled(true);
    parent.focus();
  });
  
  return modal;
}

// Usage
const confirmDialog = showModal(mainWindow, {
  url: "views://confirm/index.html",
  width: 400,
  height: 200,
});
Window Positioning
function centerWindow(window: BrowserWindow) {
  const { screen } = require("electrobun/bun");
  const display = screen.getPrimaryDisplay();
  const { width: screenWidth, height: screenHeight } = display.workArea;
  const { width, height } = window.getBounds();
  
  const x = Math.floor((screenWidth - width) / 2);
  const y = Math.floor((screenHeight - height) / 2);
  
  window.move({ x, y });
}

function cascadeWindows(windows: BrowserWindow[]) {
  const offset = 30;
  windows.forEach((win, index) => {
    win.move({ 
      x: 100 + (index * offset), 
      y: 100 + (index * offset) 
    });
  });
}

Resources

For more on Electrobun:

  • Core skill: electrobun - Basic window creation and APIs
  • RPC patterns: electrobun-rpc-patterns - Window-to-window RPC
  • Native UI: electrobun-native-ui - Menus and tray integration

Comments

Markdown supported