Off IDE

A high-performance, VS Code-like workspace shell widget for Flutter applications.

Live Demo

Off IDE Demo

Features

Feature Description
VS Code Layout Familiar structure with Activity Bar, Sidebar, and Editor Area
Split Editor Vertical split panes to view multiple tabs side-by-side
Tab Management Close actions, dirty state indicators, and drag-and-drop reordering
Unsaved Changes Guard onBeforeCloseTab callback to prompt before closing dirty tabs
Keyboard Shortcuts Browser-safe defaults (Alt+W, Alt+], Alt+[) with consumer overrides
Text Selection SelectionArea wrapping for copy-paste in browser deployments
Persistence Auto-restore open tabs and sidebar state via HydratedBloc
Web Ready Responsive design, browser-safe shortcuts, and persistence support
High Performance O(1) state lookups and granular rebuilds for large widget trees
Custom Icons Dynamic iconWidget support (SVGs, images) with tree-shaking preserved
Customizable Configurable activity bar, sidebar views, page registry, and shortcuts
Theme Aware Integrates with ThemeData, supporting light and dark modes

Getting Started

Add off_ide to your pubspec.yaml:

dependencies:
  off_ide: ^0.1.4

Usage

The main entry point is the WorkspaceShell widget, which requires a WorkspaceConfig.

There are two primary ways to define this configuration: Hardcoded (for simple, static apps) and Dynamic (for robust, backend-driven apps like CRMs).

1. Basic Usage (Hardcoded)

For simple applications where the sidebar structure never changes, you can define your WorkspaceConfig statically:

import 'package:flutter/material.dart';
import 'package:off_ide/off_ide.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: WorkspaceShell(
        config: WorkspaceConfig(
          // 1. Define Activity Bar Items
          activityBarItems: [
            const ActivityBarItem(
              id: 'files',
              icon: Icons.folder_outlined,
              label: 'Explorer',
            ),
          ],
          // 2. Define Sidebar Content
          sidebarViews: {
            'files': const SidebarView(
              id: 'files',
              title: 'EXPLORER',
              groups: [
                 MenuGroup(
                   id: 'project',
                   label: 'My Project',
                   items: [
                     MenuItem(
                       id: 'readme',
                       label: 'README.md',
                       pageId: 'markdown_viewer',
                       icon: Icons.description_outlined,
                     ),
                   ],
                 ),
              ],
            ),
          },
          // 3. Register Page Builders
          pageRegistry: {
            'markdown_viewer': (context, args) => const Center(child: Text('README')),
          },
        ),
      ),
    );
  }
}

2. Unsaved Changes Guard

Protect users from losing unsaved work by providing an onBeforeCloseTab callback:

WorkspaceConfig(
  onBeforeCloseTab: (context, tab) async {
    return await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('Unsaved Changes'),
        content: Text('"${tab.title}" has unsaved changes. Discard?'),
        actions: [
          TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
          FilledButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('Discard')),
        ],
      ),
    ) ?? false;
  },
  // ... other config
)

Mark tabs dirty from your forms via the BLoC:

context.read<WorkspaceBloc>().add(MarkTabDirty(tabId: tabId, isDirty: true));

3. Keyboard Shortcuts

Built-in browser-safe shortcuts (these don't conflict with browser hotkeys):

Shortcut Action
Alt + W Close active tab
Alt + ] Cycle to next tab
Alt + [ Cycle to previous tab

Add custom shortcuts via config:

WorkspaceConfig(
  keyboardShortcuts: {
    const SingleActivator(LogicalKeyboardKey.keyP, control: true): 
      () => showCommandPalette(context),
  },
  // ... other config
)

The WorkspaceConfig is built to be extremely flexible. For complex applications, it is highly recommended to dynamically generate your sidebar using backend-driven JSON schemas combined with a state manager (like flutter_bloc).

This approach easily enables Role-Based Access Control (RBAC).

Implementation Example

By wrapping WorkspaceShell in a state listener, the shell will seamlessly update and automatically close restricted tabs when roles change!

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Listen to your Auth/Role State Manager
    return BlocBuilder<RoleCubit, String>(
      builder: (context, currentRole) {
        return MaterialApp(
          home: WorkspaceShell(
            config: WorkspaceConfig(
              
              // 1. Parse your backend JSON and filter by `currentRole`
              activityBarItems: CustomParser.parseActivityBar(jsonSchema, currentRole),
              sidebarViews: CustomParser.parseSidebars(jsonSchema, currentRole),
              
              // 2. Only provide page builders the user has access to
              pageRegistry: CustomParser.getPageRegistry(currentRole),
            ),
          ),
        );
      },
    );
  }
}

Key Dynamic Features:

  1. Discard Restricted Items: Overwrite your CustomParser to check if a user has permission to see a JSON item. If not, don't instantiate the MenuItem.
  2. Auto-Purge Tabs: Because WorkspaceShell is deeply stateful, if the user's role changes, the shell will automatically purge any open tabs that no longer exist in the updated pageRegistry.
  3. Custom Backend Icons: Make use of the iconWidget parameter to parse custom backend icons (SVGs, Image Assets, custom Flutter code) instead of standard IconData to ensure Flutter web compilation works smoothly with tree-shaking.

See the example/lib/main.dart source code to view a complete, production-ready implementation of dynamic JSON parsing and an interactive RBAC role toggle.

Additional Resources

Check out the example directory for a complete, runnable sample application.

License

MIT

Libraries

off_ide
A VS Code-like workspace shell for Flutter CRM/ERP applications