Launch OpenGPEX

OpenGPEX 1.x documentation (legacy editor).

v2 Docs

Core Hooks

React hooks exposed by the context to bind plugin components to state, commands, signals, and config.

All hooks are imported from @opengpex/editor/core/context.


useEditorServices()

Accesses stable service facades and command executors. Does not cause component re-renders when states change.

API Specification

  • Input: None.
  • Internal Logic: Resolves EditorServiceContext values. If inside a plugin context, wraps actions into a scoped proxy that automatically prefixes local command IDs with the plugin's UID.
  • Output: An object of type EditorServiceContextValue containing:
    • actions: Scoped facade containing dispatchers, command bus, and adv.* system facades.
    • geometry: GeometryService — bounding boxes, hit testing, alignment.
    • pixels: PixelService — coordinate projection, composite, decode, rasterize, filter.
    • layers: LayerService — safe layer add/remove/activate with composite expansion.
    • assets: AssetService — CAS blob registry, URL management.
    • clipboard: ClipboardService — system clipboard interaction.
    • plugins: PluginService — plugin lifecycle management, busy state.
    • files: FileService — format detection, unified decode/encode with metadata.
    • fonts: FontService — font persistence and registration.
    • storage: StateStorage — state save/restore.
    • volatileRef: MutableRefObject — direct mutable ref for 60fps volatile state.
    • coreVersion: Engine core version string.

Example

import { useEditorServices } from '@opengpex/editor/core/context';

function MyPlugin() {
  const { actions, pixels } = useEditorServices();
  
  const handleAction = () => {
    // Within plugin scope — short ID auto-resolves:
    // executes: "author.plugin_id.cmd.reset"
    actions.executeCommand('cmd.reset');
  };

  // Use the adv facade for system-level operations
  const handleFit = () => {
    actions.adv.viewport.translate.fit.execute();
  };
}

useEditorState()

Subscribes to the reactive global state tree, causing components to re-render when values update.

API Specification

  • Input: None.
  • Internal Logic: Subscribes to the reactive EditorStateContext slice. If inside a plugin context, injects a scoped getSignal(key, default) helper that auto-prefixes the plugin UID.
  • Output: An object containing:
    • state: EditorState — full reducer state (frames, layers, pluginConfig, interaction signals).
    • activeFrame: Frame | null — currently active frame/artboard.
    • activeLayer: Layer | null — active layer inside current frame.
    • getSignal: <T>(key: string, defaultValue?: T) => T — scoped signal reader (auto-prefixes plugin UID within plugin scope).

Example

import { useEditorState } from '@opengpex/editor/core/context';

function MyPlugin() {
  const { activeFrame, activeLayer } = useEditorState();
  if (!activeFrame) return <div>No active frame</div>;
  return <div>Active Frame: {activeFrame.id} ({activeFrame.canvas.w}×{activeFrame.canvas.h})</div>;
}

⚠️ Only destructure the specific fields you need. Subscribing to the entire state object causes excessive re-renders on any state change.


usePluginSelfConfig()

Reads and writes persistent configurations for the calling plugin. Zero arguments — auto-resolves the plugin UID from PluginContext.

API Specification

  • Input: None.
  • Internal Logic: Resolves the plugin's UID from PluginContext and delegates to usePluginConfig. Merges the plugin's initialConfig with any dynamic overrides stored in state.pluginConfig[uid].
  • Output: A tuple [config: T, setConfig: (patch: Partial<T>) => void].

Example

import { usePluginSelfConfig } from '@opengpex/editor/core/context';

interface MyConfig {
  brushSize: number;
  mode: 'draw' | 'erase';
}

function BrushSettings() {
  const [config, setConfig] = usePluginSelfConfig<MyConfig>();
  return (
    <input 
      type="number" 
      value={config.brushSize || 5} 
      onChange={(e) => setConfig({ brushSize: Number(e.target.value) })}
    />
  );
}

usePluginConfig(pluginId: string)

Accesses and writes dynamic configurations for a specific target plugin UID.

API Specification

  • Input:
    • pluginId: string — The UID of the target plugin (e.g. 'opengpex.drawers.adjustment').
  • Internal Logic: Subscribes to state.pluginConfig[pluginId] and merges it with the plugin's registered initialConfig.
  • Output: A tuple [config: T, setConfig: (patch: Partial<T>) => void].

Example

const [config, setConfig] = usePluginConfig<CropConfig>('opengpex.options.clip_options');

⚠️ Directly modifying another plugin's config is discouraged. Prefer cross-plugin commands for communication.


usePluginCommands()

Provides a mapping of normalized camelCase callable wrappers for all commands declared by the current plugin.

API Specification

  • Input: None.
  • Internal Logic: Iterates over commands in the plugin's registry scope, converts command IDs (e.g. cmd.my_action) to camelCase + Cmd suffix (e.g. myActionCmd), and returns execution objects.
  • Output: An object dictionary where each key maps to a command instance with:
    • .execute(payload?) — Execute the command
    • .name — Human-readable command name
    • .shortcutLabel — Formatted keyboard shortcut string (e.g. "⌘Z")

Naming Rules

  1. Split cmd.id by delimiters (., _, -)
  2. Remove leading "cmd" token if present
  3. CamelCase remaining tokens
  4. Append "Cmd" suffix

Example

import { usePluginCommands } from '@opengpex/editor/core/context';

function MyPanel() {
  const { resetBoxCmd, toggleMaskCmd } = usePluginCommands();
  
  return (
    <div>
      <button onClick={() => resetBoxCmd.execute()}>
        Reset ({resetBoxCmd.shortcutLabel})
      </button>
      <button onClick={() => toggleMaskCmd.execute({ visible: true })}>
        Show Mask
      </button>
    </div>
  );
}

usePluginSignals()

Provides reactive controllers for all state signals declared in the current plugin's scope.

API Specification

  • Input: None.
  • Internal Logic: Scans the plugin's registered signals, converts keys (e.g. signal.active_tab) to camelCase + Signal suffix (e.g. activeTabSignal), and links them to reactive get/set controllers.
  • Output: An object map of signal controllers, each with:
    • .value — Reactive getter for the current signal value
    • .set(val) — Setter to update the signal value

Example

import { usePluginSignals } from '@opengpex/editor/core/context';

function MyPanel() {
  const { activeTabSignal, panelOpenSignal } = usePluginSignals();
  
  return (
    <div>
      <p>Current tab: {activeTabSignal.value}</p>
      <button onClick={() => activeTabSignal.set('curves')}>Curves</button>
      <button onClick={() => panelOpenSignal.set(!panelOpenSignal.value)}>Toggle</button>
    </div>
  );
}

usePluginResource(relativePath: string)

Resolves relative paths to assets inside the plugin directory.

API Specification

  • Input:
    • relativePath: string — Path of the asset relative to the plugin folder root.
  • Internal Logic: Resolves the plugin's _folderName from PluginContext and constructs the serve API URL.
  • Output: string — URL path: /api/plugins/serve/${folderName}/${relativePath}

Example

import { usePluginResource } from '@opengpex/editor/core/context';

function MyPanel() {
  const iconUrl = usePluginResource('visuals/icon.svg');
  return <img src={iconUrl} alt="Plugin Icon" />;
}

usePluginList()

Subscribes reactively to the list of all registered plugins. Re-renders when plugins are registered, unregistered, enabled, or disabled.

API Specification

  • Input: None.
  • Internal Logic: Uses useSyncExternalStore to subscribe to PluginService notifications.
  • Output: BuiltPlugin[] — Current snapshot of all registered plugins.

usePluginSelfBusy()

Reactively reads the busy state of the current plugin.

API Specification

  • Input: None.
  • Internal Logic: Uses useSyncExternalStore to subscribe to the PluginService's busy set, filtered to the current plugin's UID.
  • Output: boolean — true when ctx.scoped.setBusy(true) has been called.

Example

import { usePluginSelfBusy } from '@opengpex/editor/core/context';

function GenerateButton() {
  const isBusy = usePluginSelfBusy();
  return (
    <button disabled={isBusy}>
      {isBusy ? 'Processing...' : 'Generate'}
    </button>
  );
}

When busy, the sidebar drawer icon automatically displays a rainbow gradient breathing animation.


useVolatileInteraction(key)

Subscribe to a specific field of the volatile interaction store without global re-renders.

API Specification

  • Input:
    • key: keyof VolatileInteraction — The specific interaction field to subscribe to.
  • Internal Logic: Uses useSyncExternalStore with field-level granularity. Only triggers re-render when the subscribed field changes.
  • Output: The current value of the specified volatile interaction field.

Example

import { useVolatileInteraction } from '@opengpex/editor/core/context';

function CursorInfo() {
  const hoveredId = useVolatileInteraction('hoveredLayerId');
  const cursor = useVolatileInteraction('cursorOverride');
  return <div>Hovered: {hoveredId}, Cursor: {cursor}</div>;
}

Last updated: 2026-07-30