# Metabox Modular Configurator API

TypeScript/JavaScript API for integrating Metabox Modular Configurator into web applications. Provides programmatic control over modular product configurations, component assembly, materials, environments, and export functionality.

## Table of Contents

- [Features](#features)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
  - [NPM Installation (Recommended)](#npm-installation-recommended)
  - [CDN Installation (Quick Prototyping)](#cdn-installation-quick-prototyping)
- [Quick Start](#quick-start)
  - [1. HTML Setup](#1-html-setup)
  - [2. Basic Integration](#2-basic-integration)
    - [JavaScript (ES6 Modules)](#javascript-es6-modules)
    - [TypeScript/Angular/React](#typescriptangularreact)
- [API Reference](#api-reference)
  - [System Terminology](#system-terminology)
  - [Core Integration — `integrateMetabox()`](#core-integration--integratemetabox)
  - [Type Definitions](#type-definitions)
    - [Core Types](#core-types)
    - [Utility Types](#utility-types)
  - [Command Classes](#command-classes)
    - [Component & Environment Management](#component--environment-management)
    - [UI Control](#ui-control)
    - [Camera Control](#camera-control)
    - [Showcase/Animation Control](#showcaseanimation-control)
    - [Measurement Tools](#measurement-tools)
    - [Stream Control](#stream-control)
    - [Export & Media](#export--media)
  - [Event Handling](#event-handling)
    - [Available Events](#available-events)
    - [Event Examples](#event-examples)
- [Standalone Mode](#standalone-mode)
- [Building a Custom UI](#building-a-custom-ui)
  - [Building a Custom Menu (Preferred Approach)](#building-a-custom-menu-preferred-approach)
  - [Export Configuration](#export-configuration)
- [Troubleshooting](#troubleshooting)
  - [Common Issues](#common-issues)
    - [1. Configurator Not Loading](#1-configurator-not-loading)
    - [2. Commands Not Working](#2-commands-not-working)
- [Tips & Tricks](#tips--tricks)

## Features

- Framework-agnostic integration (Angular, React, Vue, vanilla JS)
- Modular component assembly and configuration
- Material and environment management per component
- Real-time configuration state synchronization
- Screenshot and PDF export capabilities
- E-commerce CTA workflow support
- Showcase/animation playback control
- Full TypeScript type definitions
- CDN support for rapid prototyping
- Event-driven architecture for reactive UI

## Prerequisites

**Runtime Requirements:**

- Modern browser with ES6 module support (Chrome 61+, Firefox 60+, Safari 11+, Edge 79+)
- HTTPS connection (required by Unreal Engine streaming)
- Valid Metabox modular configurator ID

**Development Environment:**

- Node.js >= 20 (for NPM installation)
- npm > 9

**Important Notes:**

- `integrateMetabox()` validates inputs at runtime
- Requires non-empty `configuratorId` and `containerId`
- Iframe URL format: `https://{domain}/metabox-configurator/modular/{configuratorId}`
- Optional query parameters: `introImage`, `introVideo`, `loadingImage`, `showLoadingProgress`, `showResumeStreamPopup`, `showUserInactivityTimer`, `userInactivityTimeout`
- HTTPS is strictly enforced (HTTP URLs will be rejected)
- Throws error if container element not found in DOM

## Installation

### NPM Installation (Recommended)

**Installation:**

```bash
npm install @3dsource/metabox-modular-configurator-api@latest --save
```

**Import:**

```typescript
import { integrateMetabox, Communicator, ModularConfiguratorEnvelope, SetComponent, SetComponentMaterial, SetEnvironment, GetScreenshot, saveImage } from '@3dsource/metabox-modular-configurator-api';
```

### CDN Installation (Quick Prototyping)

**jsDelivr:**

```javascript
import { integrateMetabox, SetComponent, SetEnvironment, GetScreenshot, saveImage } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-modular-configurator-api@latest/+esm';
```

> **Note:** For production, pin a specific version instead of using `@latest`

## Quick Start

### 1. HTML Setup

Ensure your HTML page has the following structure, where the Metabox Configurator will be integrated.
You must provide CSS for the `#embed3DSource` element to make the configurator responsive and fit your layout needs.
Create a container element where the configurator will be embedded:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Metabox Configurator</title>
    <style>
      #embed3DSource {
        width: 100%;
        height: 600px;
        border: 1px solid #ccc;
        border-radius: 8px;
      }
    </style>
  </head>
  <body>
    <div id="embed3DSource">
      <!-- Configurator iframe will be embedded here -->
    </div>
  </body>
</html>
```

### 2. Basic Integration

#### JavaScript (ES6 Modules)

```html
<script type="module">
  import { GetPdf, GetScreenshot, GetCallToActionInformation, integrateMetabox, saveImage, SetComponent, SetComponentMaterial, SetEnvironment, SetEnvironmentMaterial, ShowEmbeddedMenu, ShowOverlayInterface } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-modular-configurator-api@latest/+esm';

  // Replace it with your actual configurator ID (not a full URL)
  const configuratorId = 'configurator-id';

  integrateMetabox(configuratorId, 'embed3DSource', (api) => {
    console.log('Configurator API ready!');

    // Listen for configurator data updates
    api.addEventListener('configuratorDataUpdated', (data) => {
      console.log('Configurator data:', data);
    });

    // Listen for screenshot events
    api.addEventListener('screenshotReady', (data) => {
      saveImage(data, 'configurator-screenshot.png');
    });

    // Initial commands example
    api.sendCommandToMetabox(new SetComponent('componentId', 'typeId'));
    api.sendCommandToMetabox(new SetEnvironment('environmentId'));
    api.sendCommandToMetabox(new SetComponentMaterial('componentId', 'slotId', 'materialId'));
    api.sendCommandToMetabox(new SetEnvironmentMaterial('slotId', 'materialId'));
    api.sendCommandToMetabox(new ShowEmbeddedMenu(false));
    api.sendCommandToMetabox(new ShowOverlayInterface(false));
    api.sendCommandToMetabox(new GetPdf());
    api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1024, y: 1024 }));
    api.sendCommandToMetabox(new GetCallToActionInformation());
  });
</script>
```

#### TypeScript/Angular/React

```typescript
import { integrateMetabox, Communicator, ModularConfiguratorEnvelope, SetEnvironment, ShowOverlayInterface, GetScreenshot, saveImage, SetComponent, SetComponentMaterial, type MimeType, ShowEmbeddedMenu } from '@3dsource/metabox-modular-configurator-api';

class ConfiguratorIntegration {
  private api: Communicator | null = null;
  private readonly configuratorId: string;

  constructor(configuratorId: string) {
    this.configuratorId = configuratorId;
    this.initialize();
  }

  private initialize(): void {
    integrateMetabox(this.configuratorId, 'embed3DSource', (api: Communicator) => {
      this.api = api;
      this.setupEventListeners();
      this.setupInitialState();
    });
  }

  private setupEventListeners(): void {
    if (!this.api) {
      return;
    }

    // Listen for configuration data changes
    this.api.addEventListener('configuratorDataUpdated', (data: ModularConfiguratorEnvelope) => {
      console.log('Configurator data updated:', data);
      this.handleConfiguratorData(data);
    });

    // Handle screenshot events
    this.api.addEventListener('screenshotReady', (imageData: string | null) => {
      saveImage(imageData ?? '', 'configurator-render.png');
    });

    // Handle viewport ready events
    this.api.addEventListener('viewportReady', (isReady: boolean) => {
      console.log('Viewport ready:', isReady);
    });

    // Handle status messages
    this.api.addEventListener('statusMessageChanged', (message: string | null) => {
      console.log('Status message:', message);
    });
  }

  private setupInitialState(): void {
    if (!this.api) return;

    // Configure initial environment and components
    this.api.sendCommandToMetabox(new SetEnvironment('default-environment'));
    this.api.sendCommandToMetabox(new SetComponent('main-product', 'chair'));
    this.api.sendCommandToMetabox(new ShowOverlayInterface(true));
    this.api.sendCommandToMetabox(new ShowEmbeddedMenu(true));
  }

  private handleConfiguratorData(data: ModularConfiguratorEnvelope): void {
    // Access typed data properties according to actual interface
    console.log('Configurator:', data.configurator);
    console.log('Configuration tree:', data.configurationTree);
    console.log('Components by type:', data.componentsByComponentType);
    console.log('Component materials:', data.componentMaterialsIds);
    console.log('Environment ID:', data.environmentId);
    console.log('Environment materials:', data.environmentMaterialsIds);

    // Process configurator data according to your needs
    this.updateUI(data);
  }

  private updateUI(data: ModularConfiguratorEnvelope): void {
    // Update your application UI based on configurator state
    // This method would contain your specific UI update logic
  }

  // Public API methods
  public takeScreenshot(format: MimeType = 'image/png', size?: { x: number; y: number }): void {
    if (this.api) {
      this.api.sendCommandToMetabox(new GetScreenshot(format, size));
    }
  }

  public changeMaterial(componentId: string, slotId: string, materialId: string): void {
    if (this.api) {
      this.api.sendCommandToMetabox(new SetComponentMaterial(componentId, slotId, materialId));
    }
  }

  public changeEnvironment(environmentId: string): void {
    if (this.api) {
      this.api.sendCommandToMetabox(new SetEnvironment(environmentId));
    }
  }

  public destroy(): void {
    // Clean up resources when component is destroyed
    this.api = null;
  }
}

// Usage example
const configurator = new ConfiguratorIntegration('configurator-id');

// Take a high-resolution screenshot
configurator.takeScreenshot('image/png', { x: 1920, y: 1080 });

// Change material
configurator.changeMaterial('componentId', 'seat-fabric', 'leather-brown');
```

## API Reference

### System Terminology

| Term                  | Description                                                    | Context                |
| --------------------- | -------------------------------------------------------------- | ---------------------- |
| **component**         | Modular product part/assembly (e.g., wheel, bumper, seat)      | Modular configurator   |
| **componentId**       | Unique component identifier (system-generated UUID)            | Component management   |
| **componentType**     | Category/classification of component (e.g., "wheel", "bumper") | Component organization |
| **environment**       | 3D scene/room environment for product visualization            | Visual context         |
| **environmentId**     | Unique environment identifier (system-generated UUID)          | Environment management |
| **product**           | Base 3D digital twin referenced by components                  | Component definition   |
| **productId**         | Unique product identifier (system-generated UUID)              | Product reference      |
| **externalId**        | Custom SKU/identifier for external system integration          | E-commerce integration |
| **showcase**          | Camera animation sequence attached to component                | Component attribute    |
| **slotId**            | Material slot identifier on component/environment              | Material assignment    |
| **materialId**        | Unique material identifier                                     | Material assignment    |
| **configurationTree** | Hierarchical structure of component assembly                   | State representation   |

**E-Commerce Configurator (CTA Integration):**

E-Com configurators extend Modular configurators with a call-to-action button.

**Configuration:**

1. **CTA Button Label:** Custom text (e.g., "Request Quote", "Add to Cart")
2. **Callback URL:** HTTP POST endpoint for configuration data

**CTA Workflow:**

```
User clicks CTA → Metabox POSTs config JSON → Backend processes → Returns { redirectUrl } → User redirected
```

**Backend Response Format:**

```json
{
  "redirectUrl": "https://your.site/checkout-or-thank-you"
}
```

**Use Cases:**

- Send lead to CRM with component details
- Redirect to custom checkout with modular pricing

### Core Integration — `integrateMetabox()`

Embeds the Metabox Modular Configurator iframe and establishes communication channel.

**Signature:**

```typescript
function integrateMetabox(configuratorId: string, containerId: string, apiReadyCallback: (api: Communicator) => void, config?: IntegrateMetaboxConfig): void;
```

**Parameters:**

| Parameter          | Type                     | Required | Default | Description                              |
| ------------------ | ------------------------ | -------- | ------- | ---------------------------------------- |
| `configuratorId`   | `string`                 | ✅       | -       | Modular configurator UUID (not full URL) |
| `containerId`      | `string`                 | ✅       | -       | DOM container element ID                 |
| `apiReadyCallback` | `function`               | ✅       | -       | Callback invoked when API is ready       |
| `config`           | `IntegrateMetaboxConfig` | ❌       | `{}`    | Additional configuration options         |

**Config Options (`IntegrateMetaboxConfig`):**

```typescript
interface IntegrateMetaboxConfig {
  standalone?: boolean; // Disable built-in Metabox UI
  introImage?: string; // URL for intro image
  introVideo?: string; // URL for intro video
  loadingImage?: string; // URL for loading spinner
  state?: string; // Initial component tree state (rison format)
  domain?: string; // Override domain (HTTPS only)
  showLoadingProgress?: boolean; // Show loading progress bar inside the configurator
  showResumeStreamPopup?: boolean; // Show resume stream popup when the stream is paused
  showUserInactivityTimer?: boolean; // Show user inactivity countdown popup before disconnecting
  userInactivityTimeout?: number; // Inactivity timeout in seconds before the user is warned and disconnected
}
```

**Validation & Errors:**

- Throws if `configuratorId` is empty
- Throws if a container element not found
- Throws if URL is not HTTPS
- Replaces existing iframe with ID `embeddedContent`

**Example:**

```typescript
integrateMetabox(
  'modular-config-uuid',
  'my-container',
  (api) => {
    console.log('Modular configurator ready');

    // Work with API here, e.g., listen to events or send commands
  },
  {
    standalone: true,
    loadingImage: 'https://cdn.example.com/loader.gif',
  },
);
```

### Type Definitions

The API provides comprehensive TypeScript support with the following key interfaces and types:

#### Core Types

##### `Communicator`

The main API interface for sending commands and listening to events.

```typescript
class Communicator extends EventDispatcher {
  sendCommandToMetabox<T extends CommandBase>(command: T): void;
  addEventListener<T extends FromMetaBoxApiEvents>(messageType: T, callback: (data: FromMetaboxMessagePayloads[T]) => void): this;
  removeEventListener<T extends FromMetaBoxApiEvents>(messageType: T, callback: (data: FromMetaboxMessagePayloads[T]) => void): this;
}
```

##### `ModularConfiguratorEnvelope`

The main data structure contains the complete configurator state.

```typescript
interface ModularConfiguratorEnvelope {
  /** Represents the modular configurator api with its components, component types, and environments */
  configurator: InitialModularConfigurator;
  /** The unique tree of components and parent and component type id */
  configurationTree: ComponentWithParent[];
  /** A record mapping all components by current component type */
  componentsByComponentType: Record<string, string>;
  /** A record mapping component material selection */
  componentMaterialsIds: SelectedIds;
  /** The unique environment identifier */
  environmentId: string;
  /** A record mapping environment material selections */
  environmentMaterialsIds: SelectedIds;
}
```

##### `ModularConfiguratorComponent`

Represents a component in the configurator.

```typescript
interface ModularConfiguratorComponent {
  /** The id unique identifier. */
  id: string;
  /** The modular configurator component type identifier. */
  componentType: ModularConfiguratorComponentType;
  /** Display the name for the product. */
  name: string;
  /** Position index of the product. */
  position: number;
  /** The detailed product information. */
  product: Product;
  /** Connectors between connectors and virtual sockets. */
  virtualSocketToConnectorsMap?: ModularConfiguratorVirtualSocketToConnector[];
  /** GraphQL typename for the component. */
  __typename: 'ModularConfiguratorComponent';
}
```

##### `Material`

Represents a material that can be applied to components or environments.

```typescript
interface Material {
  /** Unique identifier for the material. */
  id: string;
  /** Title or name of the material. */
  title: string;
  /** Unique external id for the material. */
  externalId: string;
  /** List of thumbnails for the material. */
  thumbnailList: Thumbnail[];
  /** GraphQL typename for the material. */
  __typename: 'Material';
}
```

##### `Environment`

Represents an environment/scene configuration.

```typescript
interface Environment {
  /** Unique identifier for the environment. */
  id: string;
  /** Title or name of the environment. */
  title: string;
  /** Flag indicating if UDS is enabled. */
  udsEnabled: boolean;
  /** The north yaw of the sun in the environment. */
  sunNorthYaw: number;
  /** The UDS hour setting for the environment. */
  udsHour: number;
  /** List of thumbnails for the environment (optional). */
  thumbnailList: Thumbnail[];
  /** List of slots for the environment (optional). */
  slots: Slot[];
  /** GraphQL typename for the environment. */
  __typename: 'Environment';
}
```

##### `InitialModularConfigurator`

The full configurator definition containing all available components, environments, and component types.

```typescript
interface InitialModularConfigurator {
  /** Unique identifier for the configurator. */
  id: string;
  /** The name of the configurator. */
  name: string;
  /** A list of components for the configurator. */
  components: ModularConfiguratorComponent[];
  /** A list of available environments. */
  environments: ModularConfiguratorEnvironment[];
  /** A list of component types for the configurator. */
  componentTypes: ModularConfiguratorComponentType[];
  /** Indicates if the configurator is active. */
  isActive: boolean;
  /** Indicates if the CTA in ecom configurator is enabled. */
  ctaEnabled: boolean;
  /** A list of connectors showing connections between component types. */
  connectors: ModularConfiguratorConnector[];
  __typename: 'ModularConfigurator';
}
```

##### `ModularConfiguratorComponentType`

Represents a component category/classification (e.g., "wheel", "bumper", "seat").

```typescript
interface ModularConfiguratorComponentType {
  /** Display name for the component type. */
  name: string;
  /** Position index of the component type. */
  position: number;
  /** Is root component type. */
  isRoot: boolean;
  /** Is required component type. */
  isRequired: boolean;
  /** Unique identifier. */
  id: string;
  __typename: 'ModularConfiguratorComponentType';
}
```

##### `Product`

Base 3D digital twin referenced by each component.

```typescript
interface Product {
  /** The title of the product. */
  title: string;
  /** List of thumbnails for the product. */
  thumbnailList: Thumbnail[];
  /** Available material slots for the product. */
  slots: Slot[];
  /** List of meta properties for the product. */
  metaProperties: MetaProperty[];
  /** List of virtual sockets for modular assembly connections. */
  virtualSockets: VirtualSocket[];
  /** Unique identifier for the product. */
  id: string;
  /** External SKU/identifier for ERP/PIM integration. */
  externalId: string;
  /** Showcase animation details (optional). */
  showcase?: Showcase;
  __typename: 'Product';
}
```

##### `Slot`

A material slot on a component or environment — a customizable surface.

```typescript
interface Slot {
  /** Unique identifier for the slot. */
  id: string;
  /** Display label for the slot. */
  label: string;
  /** Available materials for this slot. */
  enabledMaterials: Material[];
  __typename: 'Slot';
}
```

##### `ModularConfiguratorEnvironment`

Environment wrapper containing position and label for environment selector UIs.

```typescript
interface ModularConfiguratorEnvironment {
  /** Unique identifier for the environment. */
  environmentId: string;
  /** Position index of the environment. */
  position: number;
  /** Display label for the environment. */
  label: string;
  /** Detailed environment configuration. */
  environment: Environment;
  __typename: 'ModularConfiguratorEnvironment';
}
```

##### `ComponentWithParent`

Represents a node in the component assembly tree hierarchy.

```typescript
interface ComponentWithParent {
  id: string;
  componentTypeId: string;
  parentId: string | null;
}
```

##### `SelectedIds`

Record type tracking currently selected material/component IDs.

```typescript
type SelectedIds = Record<string, Record<string, string>>;
```

##### `ShowCaseStatus`

Showcase animation playback status.

```typescript
type ShowCaseStatus = 'init' | 'play' | 'pause' | 'stop';
```

##### `ModularConfiguratorConnector`

Defines connection rules between component types for modular assembly.

```typescript
interface ModularConfiguratorConnector {
  id: string;
  componentTypeId1: string;
  componentTypeId2: string;
  __typename: 'ModularConfiguratorConnector';
}
```

#### Utility Types

##### `MimeType`

Supported image formats for screenshots.

```typescript
type MimeType = 'image/png' | 'image/jpeg' | 'image/webp';
```

### Command Classes

Commands control configurator behavior. Send via `api.sendCommandToMetabox(new CommandName(...))`.

#### Component & Environment Management

| Command                  | Parameters                                                | Description                                                               |
| ------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------- |
| `SetComponent`           | `componentId: string, typeId: string`                     | Add/select component in assembly                                          |
| `SetComponentMaterial`   | `componentId: string, slotId: string, materialId: string` | Apply material to component slot                                          |
| `SetEnvironment`         | `environmentId: string`                                   | Change environment/scene                                                  |
| `SetEnvironmentMaterial` | `slotId: string, materialId: string`                      | Apply material to environment slot                                        |
| `ResetConfiguration`     | None                                                      | Reset configurator state to defaults (components, materials, environment) |

**Component Assembly Example:**

```typescript
// Set root component (e.g., vehicle chassis)
api.sendCommandToMetabox(new SetComponent('chassis-001', 'base'));

// Add child component (e.g., wheel to chassis)
api.sendCommandToMetabox(new SetComponent('wheel-fr', 'wheel'));

// Apply materials
api.sendCommandToMetabox(new SetComponentMaterial('chassis-001', 'body', 'metallic-blue'));
api.sendCommandToMetabox(new SetComponentMaterial('wheel-fr', 'rim', 'chrome'));

// Restore the assembly back to the template defaults
api.sendCommandToMetabox(new ResetConfiguration());
```

#### UI Control

- Note: ShowEmbeddedMenu and ShowOverlayInterface commands are ineffective in standalone mode.

| Command                | Parameters         | Description                  |
| ---------------------- | ------------------ | ---------------------------- |
| `ShowEmbeddedMenu`     | `visible: boolean` | Show/hide right sidebar menu |
| `ShowOverlayInterface` | `visible: boolean` | Show/hide viewport controls  |

#### Camera Control

| Command       | Parameters                     | Description                                                 |
| ------------- | ------------------------------ | ----------------------------------------------------------- |
| `SetCamera`   | `camera: CameraCommandPayload` | Set camera position, rotation, fov and restrictions         |
| `GetCamera`   | None                           | Request current camera; returns via `getCameraResult` event |
| `ResetCamera` | None                           | Reset camera to default position                            |
| `ApplyZoom`   | `zoom: number`                 | Apply zoom delta (positive=in, negative=out)                |

**Example:**

```typescript
api.sendCommandToMetabox(new ApplyZoom(50)); // Zoom in
api.sendCommandToMetabox(new ApplyZoom(-25)); // Zoom out
api.sendCommandToMetabox(new ResetCamera()); // Reset view
api.sendCommandToMetabox(
  new SetCamera({
    fov: 45,
    mode: 'orbit',
    position: { x: 100, y: 100, z: 100 },
    rotation: { horizontal: 0, vertical: 0 },
    restrictions: {
      maxDistanceToPivot: 0,
      maxFov: 0,
      maxHorizontalRotation: 0,
      maxVerticalRotation: 0,
      minDistanceToPivot: 0,
      minFov: 0,
      minHorizontalRotation: 0,
      minVerticalRotation: 0,
    },
  }),
);

// Request current camera state
api.sendCommandToMetabox(new GetCamera());

// Listen for the camera data
api.addEventListener('getCameraResult', (camera) => {
  console.log('Current camera:', camera);
  // camera is of type CameraCommandPayload
});
```

##### `CameraCommandPayload`

The payload used by the `SetCamera` command to configure the camera.

```typescript
interface CameraCommandPayload {
  fov?: number;
  mode?: 'fps' | 'orbit';
  position?: { x: number; y: number; z: number };
  rotation?: {
    horizontal: number;
    vertical: number;
  };
  restrictions?: {
    maxDistanceToPivot?: number;
    maxFov?: number;
    maxHorizontalRotation?: number;
    maxVerticalRotation?: number;
    minDistanceToPivot?: number;
    minFov?: number;
    minHorizontalRotation?: number;
    minVerticalRotation?: number;
  };
}
```

- `fov` (number): Field of view in degrees.
- `mode` ('fps' | 'orbit'):
  - `fps`: First‑person style camera, moves freely in space.
  - `orbit`: Orbiting camera around a pivot (typical product viewer behavior).
- `position` ({ x, y, z }): Camera position in world coordinates.
- `rotation` ({ horizontal, vertical }): Camera rotation angles in degrees.
  - `horizontal`: Yaw (left/right).
  - `vertical`: Pitch (up/down).
- `restrictions` (optional): Limits applied by the viewer to constrain user/camera movement.
  - Distance limits (orbit mode): `minDistanceToPivot`, `maxDistanceToPivot`.
  - Field‑of‑view limits: `minFov`, `maxFov`.
  - Rotation limits: `minHorizontalRotation`, `maxHorizontalRotation`, `minVerticalRotation`, `maxVerticalRotation`.

Notes:

- Provide only the limits you want to enforce; unspecified values are left as currently configured by the viewer.
- Rotation units are degrees; positive/negative values follow the viewer's right‑handed coordinate system.
- In `orbit` mode, distance limits are interpreted relative to the orbit pivot.
- `ResetCamera` restores the default position/rotation/FOV defined by the current product or environment template.

#### Showcase/Animation Control

| Command         | Parameters | Description                               |
| --------------- | ---------- | ----------------------------------------- |
| `InitShowcase`  | None       | Initialize showcase for current component |
| `PlayShowcase`  | None       | Start/resume animation playback           |
| `PauseShowcase` | None       | Pause animation                           |
| `StopShowcase`  | None       | Stop and reset animation                  |

#### Measurement Tools

| Command           | Parameters | Description                  |
| ----------------- | ---------- | ---------------------------- |
| `ShowMeasurement` | None       | Display component dimensions |
| `HideMeasurement` | None       | Hide dimension overlay       |

#### Stream Control

| Command                    | Parameters | Description                                                 |
| -------------------------- | ---------- | ----------------------------------------------------------- |
| `ResumeStream`             | None       | Resume pixel streaming session after pause or disconnection |
| `ResetUserInactivityTimer` | None       | Reset the AFK inactivity timer to keep the session alive    |

**Example:**

```typescript
// Resume the stream after an idle timeout or network interruption
api.sendCommandToMetabox(new ResumeStream());

// Reset the inactivity timer when user confirms they are still present
api.sendCommandToMetabox(new ResetUserInactivityTimer());
```

#### Export & Media

| Command                      | Parameters                                        | Description          | Event Triggered        |
| ---------------------------- | ------------------------------------------------- | -------------------- | ---------------------- |
| `GetScreenshot`              | `format: MimeType, size?: {x: number, y: number}` | Render screenshot    | `screenshotReady`      |
| `GetPdf`                     | None                                              | Generate PDF export  | Server-side (no event) |
| `GetCallToActionInformation` | None                                              | Trigger CTA workflow | Backend redirect       |

##### `saveImage()`

Helper function: triggers a browser download of an image from a data URL or blob URL.

**Signature:**

```typescript
function saveImage(imageUrl: string, filename: string): void;
```

| Parameter  | Type     | Description                         |
| ---------- | -------- | ----------------------------------- |
| `imageUrl` | `string` | Base64 data URL or blob URL to save |
| `filename` | `string` | Suggested filename for the download |

**Screenshot Example:**

```typescript
api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 2048, y: 2048 }));

api.addEventListener('screenshotReady', (imageData) => {
  saveImage(imageData, 'assembly.png');
});
```

### Event Handling

Event-driven architecture for reactive state management. Register listeners via `api.addEventListener(eventName, handler)`.

#### Available Events

| Event                         | Payload Type                                                          | Description                         | Use Case                              |
| ----------------------------- | --------------------------------------------------------------------- | ----------------------------------- | ------------------------------------- |
| `configuratorDataUpdated`     | `ModularConfiguratorEnvelope`                                         | Configuration state changed         | Sync UI with component tree/materials |
| `ecomConfiguratorDataUpdated` | `EcomConfigurator`                                                    | CTA configuration loaded            | Display CTA button with label         |
| `viewportReady`               | `boolean`                                                             | 3D viewport ready state             | Hide loading, enable interactions     |
| `showcaseStatusChanged`       | `ShowCaseStatus`                                                      | Animation playback status           | Update play/pause button state        |
| `statusMessageChanged`        | `string \| null`                                                      | Loading/progress message            | Display user feedback                 |
| `screenshotReady`             | `string \| null`                                                      | Base64 screenshot data              | Download or display image             |
| `getCameraResult`             | `CameraCommandPayload`                                                | Current camera data returned        | Capture/store current camera          |
| `videoResolutionChanged`      | `{width: number\|null, height: number\|null}`                         | Stream resolution changed           | Adjust viewport layout                |
| `videoStatsUpdated`           | `VideoStats`                                                          | Video stream quality metrics        | QoS dashboards, adaptive behavior     |
| `loadingStatusChanged`        | `{ percents: number; loaderVisible: boolean }`                        | Loading progress or overlay changed | Show custom loading progress bar      |
| `userInactivityDetected`      | `{ remainingSecondsToDisconnect: number; countdownVisible: boolean }` | AFK countdown started or updated    | Show "Stay connected?" prompt         |

#### Event Examples

**Configuration State Sync:**

```typescript
api.addEventListener('configuratorDataUpdated', (data: ModularConfiguratorEnvelope) => {
  console.log('Environment:', data.environmentId);
  console.log('Component tree:', data.configurationTree);
  console.log('Components by type:', data.componentsByComponentType);
  console.log('Component materials:', data.componentMaterialsIds);

  // Update custom UI
  updateComponentTree(data.configurationTree);
  updateMaterialSelectors(data.componentMaterialsIds);
});
```

**Screenshot Handling:**

```typescript
api.addEventListener('screenshotReady', (imageData: string | null) => {
  if (!imageData) {
    console.error('Screenshot failed');
    return;
  }

  saveImage(imageData, `assembly-${Date.now()}.png`);
});
```

**Loading State:**

```typescript
api.addEventListener('viewportReady', (isReady: boolean) => {
  if (isReady) {
    hideLoadingSpinner();
    enableComponentSelectors();
  }
});

api.addEventListener('statusMessageChanged', (message: string | null) => {
  document.getElementById('status-text').textContent = message || '';
});
```

**Showcase Control:**

```typescript
api.addEventListener('showcaseStatusChanged', (status: ShowCaseStatus) => {
  const playButton = document.getElementById('play-btn');

  switch (status) {
    case 'play':
      playButton.textContent = 'Pause';
      break;
    case 'pause':
    case 'stop':
      playButton.textContent = 'Play';
      break;
  }
});
```

**Loading Progress:**

```typescript
api.addEventListener('loadingStatusChanged', ({ percents, loaderVisible }) => {
  const bar = document.getElementById('progress-bar');
  if (bar) {
    bar.style.display = loaderVisible ? 'block' : 'none';
    bar.style.width = `${percents}%`;
  }
});
```

**AFK Inactivity Countdown:**

```typescript
import { ResetUserInactivityTimer } from '@3dsource/metabox-modular-configurator-api';

api.addEventListener('userInactivityDetected', ({ remainingSecondsToDisconnect, countdownVisible }) => {
  const dialog = document.getElementById('afk-dialog');
  const countdown = document.getElementById('afk-countdown');

  if (dialog) dialog.style.display = countdownVisible ? 'flex' : 'none';
  if (countdown) countdown.textContent = String(remainingSecondsToDisconnect);
});

document.getElementById('stay-connected-btn')?.addEventListener('click', () => {
  api.sendCommandToMetabox(new ResetUserInactivityTimer());
});
```

### Utility Functions

#### `saveImage(imageUrl: string, filename: string): void`

Downloads base64-encoded image to user's device.

**Parameters:**

| Parameter  | Type     | Description                                         |
| ---------- | -------- | --------------------------------------------------- |
| `imageUrl` | `string` | Base64 data URL (e.g., `data:image/png;base64,...`) |
| `filename` | `string` | Desired filename with extension                     |

**Example:**

```typescript
api.addEventListener('screenshotReady', (imageData: string | null) => {
  if (imageData) {
    saveImage(imageData, `config-${Date.now()}.png`);
  }
});
```

**Implementation Note:**
Creates a temporary anchor element with `download` attribute to trigger browser download.

#### `fromCommunicatorEvent(target: Communicator, eventName: string): Observable`

RxJS wrapper for Communicator events. Works like RxJS `fromEvent` — creates a typed Observable that emits each time the specified event fires. Alternative to `api.addEventListener()`.

**Parameters:**

| Parameter   | Type           | Description                                  |
| ----------- | -------------- | -------------------------------------------- |
| `target`    | `Communicator` | The Communicator instance to listen on       |
| `eventName` | `string`       | Event name (e.g., `configuratorDataUpdated`) |

**Returns:** `Observable<T>` — typed Observable matching the event payload.

**Example:**

```typescript
import { fromCommunicatorEvent } from '@3dsource/metabox-modular-configurator-api';

integrateMetabox('configurator-id', 'embed3DSource', (api) => {
  fromCommunicatorEvent(api, 'configuratorDataUpdated').subscribe((data) => {
    console.log('Components:', data.componentsByComponentType);
  });

  fromCommunicatorEvent(api, 'screenshotReady').subscribe((imageData) => {
    if (imageData) saveImage(imageData, 'screenshot.png');
  });
});
```

## Standalone Mode

Headless rendering mode for custom UI implementations. Disables all built-in Metabox UI elements. Pass `{ standalone: true }` as the config option to `integrateMetabox()`.

### When to Use Standalone Mode

✅ **Use When:**

- Building fully custom modular configurator interface
- Implementing brand-specific component selection UX
- Integrating into existing design systems
- Requiring complete control over component assembly flow

❌ **Don't Use When:**

- Quick prototyping with default UI is sufficient
- Minimal customization needed
- Limited development resources

### Behavior Changes

| Feature                   | Default Mode | Standalone Mode |
| ------------------------- | ------------ | --------------- |
| Right sidebar menu        | ✅ Visible   | ❌ Hidden       |
| Viewport overlays         | ✅ Visible   | ❌ Hidden       |
| Template logic            | ✅ Active    | ❌ Disabled     |
| API control               | ⚠️ Partial   | ✅ Full         |
| Event system              | ✅ Available | ✅ Available    |
| Component tree management | ⚠️ Shared    | ✅ Full control |

### Implementation

**TypeScript:**

```typescript
import { integrateMetabox, Communicator } from '@3dsource/metabox-modular-configurator-api';

integrateMetabox(
  'modular-config-id',
  'embed3DSource',
  (api: Communicator) => {
    // Work with API here
  },
  { standalone: true }, // <- Enable standalone mode to disable default UI
);
```

**JavaScript (CDN):**

```html
<script type="module">
  import { integrateMetabox } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-modular-configurator-api@latest/+esm';

  integrateMetabox(
    'config-id',
    'container-id',
    (api) => {
      // Work with API here
    },
    { standalone: true }, // <- Enable standalone mode to disable default UI
  );
</script>
```

## Building a Custom UI

To create a custom modular configurator interface, use [standalone mode](#standalone-mode) and subscribe to events before sending commands.

**Important:** The components menu approach below is used only for building a custom components menu. For building a custom environments menu, iterate `configurator.environments` directly and use `environmentMaterialsIds` to show active materials by slots.

**Core Pattern:**

```typescript
import { integrateMetabox, Communicator, ModularConfiguratorEnvelope } from '@3dsource/metabox-modular-configurator-api';

integrateMetabox(
  'configurator-uuid',
  'containerId',
  (api: Communicator) => {
    // 1. Subscribe to events BEFORE sending commands
    api.addEventListener('configuratorDataUpdated', (data: ModularConfiguratorEnvelope) => {
      // data.configurator.componentTypes — available component types
      // data.configurator.connectors — available connectors
      // data.configurator.environments — scene list
      // data.configurationTree — ordered tree of active components
    });
    api.addEventListener('viewportReady', (ready) => {
      /* gate UI on this */
    });

    // 2. Send commands via api.sendCommandToMetabox(new CommandClass(...args))
  },
  { standalone: true },
);
```

### Building a Custom Menu (Preferred Approach)

The menu **must** follow the `configurationTree` order — NOT iterate `componentTypes` or components as separate flat lists.

**Data Sources from `ModularConfiguratorEnvelope`:**

| Field                       | Type                                                     | Purpose                                                                      |
| --------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `configurationTree`         | `ComponentWithParent[]`                                  | Ordered tree of active component nodes (`id`, `componentTypeId`, `parentId`) |
| `componentsByComponentType` | `Record<string, string>`                                 | Maps `componentTypeId` → active `componentId`                                |
| `componentMaterialsIds`     | `SelectedIds` (`Record<string, Record<string, string>>`) | Maps `componentId` → `{ slotId: materialId }`                                |
| `configurator`              | `InitialModularConfigurator`                             | Catalog: all `componentTypes`, `components`, `environments`, `connectors`    |

**Algorithm:**

Iterate `configurationTree` nodes. For each node:

1. **Find the component type** — `configurator.componentTypes.find(t => t.id === node.componentTypeId)`
2. **Render a component selector** — show all components of that type (from `configurator.components` filtered by `componentType.id`). The active component is `componentsByComponentType[typeId]`.
3. **Render material slots** — for the active component, show its `product.slots` (filtered to those with `enabledMaterials.length > 0`). The selected material for each slot comes from `componentMaterialsIds[componentId][slotId]`.

This produces a single ordered list interleaved as: **Type1 selector → Type1 material slots → Type2 selector → Type2 material slots → …**

**Example (Angular Signals):**

```typescript
import { computed, signal } from '@angular/core';
import type { Communicator, ModularConfiguratorEnvelope, ModularConfiguratorComponent, Slot } from '@3dsource/metabox-modular-configurator-api';
import { fromCommunicatorEvent, SetComponent, SetComponentMaterial } from '@3dsource/metabox-modular-configurator-api';

// Store envelope data as a signal (updated on each configuratorDataUpdated event)
const envelope = signal<ModularConfiguratorEnvelope | null>(null);

// Subscribe to API events
function listenApi(api: Communicator) {
  fromCommunicatorEvent(api, 'configuratorDataUpdated').subscribe((data) => {
    envelope.set(data);
  });
}

// Build menu tree as a computed signal following configurationTree order
const menuTree = computed(() => {
  const data = envelope();
  if (!data) return [];

  const { configurator, configurationTree, componentsByComponentType, componentMaterialsIds } = data;
  const menuItems: MenuEntry[] = [];

  for (const node of configurationTree) {
    const ct = configurator.componentTypes.find((t) => t.id === node.componentTypeId);
    if (!ct) continue;

    const components = configurator.components.filter((c) => c.componentType.id === ct.id).sort((a, b) => a.position - b.position);
    const activeComponentId = componentsByComponentType[ct.id];
    const activeComponent = components.find((c) => c.id === activeComponentId);

    // 1. Component type selector
    menuItems.push({
      id: ct.id,
      category: ct.name,
      options: components.map((item) => ({
        label: item.name,
        id: item.id,
        image: item.product.thumbnailList[0]?.urlPath,
        action: () => api.sendCommandToMetabox(new SetComponent(item.id, ct.id)),
      })),
      selectedId: activeComponentId,
      selectedLabel: activeComponent?.name,
    });

    // 2. Material slots for the active component
    if (activeComponent) {
      for (const slot of activeComponent.product.slots) {
        if (slot.enabledMaterials.length === 0) continue;
        const selectedMaterialId = componentMaterialsIds?.[activeComponent.id]?.[slot.id];

        menuItems.push({
          id: `${activeComponent.id}:${slot.id}`,
          category: slot.label,
          options: slot.enabledMaterials.map((mat) => ({
            label: mat.title,
            id: mat.id,
            image: mat.thumbnailList[0]?.urlPath,
            action: () => api.sendCommandToMetabox(new SetComponentMaterial(activeComponent.id, slot.id, mat.id)),
          })),
          selectedId: selectedMaterialId,
          selectedLabel: slot.enabledMaterials.find((m) => m.id === selectedMaterialId)?.title,
        });
      }
    }
  }

  return menuItems;
});
```

**Commands for Selection:**

| Action           | Command                                                 | Args                                            |
| ---------------- | ------------------------------------------------------- | ----------------------------------------------- |
| Select component | `SetComponent(id, typeId)`                              | `id`: component ID, `typeId`: component type ID |
| Select material  | `SetComponentMaterial(componentId, slotId, materialId)` | all three IDs required                          |

**Anti-Patterns:**

- **DON'T** build two separate lists (component types + material slots) — this loses tree ordering
- **DON'T** iterate `configurator.componentTypes` directly — use `configurationTree` to determine which types are active and their order
- **DON'T** show all component types — only those present as keys in `componentsByComponentType` (i.e., matching `configurationTree` nodes)

### Export Configuration

```typescript
api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
api.sendCommandToMetabox(new GetPdf());
api.sendCommandToMetabox(new GetCallToActionInformation());
```

## Troubleshooting

### Common Issues

#### 1. Configurator Not Loading

**Symptoms:** Blank screen, iframe not appearing, loading indefinitely

**Root Causes & Solutions:**

| Issue                       | Diagnostic                           | Solution                                                  |
| --------------------------- | ------------------------------------ | --------------------------------------------------------- |
| Invalid configurator ID     | Browser console shows 404 errors     | Verify modular configurator ID in Metabox admin           |
| Container not found         | `Error: Container element not found` | Ensure element exists before calling `integrateMetabox()` |
| HTTP instead of HTTPS       | Mixed content warnings               | Use HTTPS or test on localhost                            |
| Container has no dimensions | Invisible iframe (0x0)               | Set explicit width/height on container element            |
| CORS/iframe blocking        | X-Frame-Options errors               | Check domain allowlist in Metabox settings                |

**Diagnostic Code:**

```typescript
const containerId = 'embed3DSource';
const container = document.getElementById(containerId);

if (!container) {
  throw new Error(`Container #${containerId} not found`);
}

// Verify container has dimensions
const rect = container.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) {
  console.warn('⚠️ Container has no dimensions');
}

if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
  console.warn('⚠️ HTTPS required for Unreal Engine streaming');
}

integrateMetabox('config-id', containerId, (api) => {
  console.log('✅ Modular configurator loaded');
});
```

#### 2. Commands Not Working

**Symptoms:** Commands sent but no visual changes in configurator

**Common Mistakes:**

| Mistake                  | Problem             | Solution                                        |
| ------------------------ | ------------------- | ----------------------------------------------- |
| Sending before API ready | Commands ignored    | Only send in `apiReadyCallback`                 |
| Wrong component IDs      | Component not found | Verify IDs from `configuratorDataUpdated` event |
| Type mismatch            | Component rejected  | Ensure `typeId` matches available types         |

**Correct Pattern:**

```typescript
integrateMetabox('config-id', 'container', (api) => {
  // ✅ Correct: Commands sent after API ready

  // First: Set root component
  api.sendCommandToMetabox(new SetComponent('comp-1', 'chassis'));

  // Then: Add child components
  api.sendCommandToMetabox(new SetComponent('comp-2', 'wheel'));

  // Finally: Apply materials
  api.sendCommandToMetabox(new SetComponentMaterial('comp-1', 'body', 'blue'));
});

// ❌ Wrong: Command sent too early
api.sendCommandToMetabox(new SetComponent('comp-1', 'chassis'));
```

### Getting Help

If you're still experiencing issues:

1. Check the browser console for error messages
2. Verify your configurator ID in a separate browser tab
3. Test with a minimal example to isolate the issue
4. Contact support with: browser version, configurator URL, console errors, and steps to reproduce

## Tips & Tricks

- **Debounce rapid commands.** Avoid sending too many commands in quick succession — debounce material or component changes (e.g., 300 ms).
- **Responsive container.** Set the configurator container to responsive dimensions (`width: 100%; height: 100%;`) and adjust with media queries for mobile.
- **Events work in all modes.** `configuratorDataUpdated`, `screenshotReady`, and other events fire in both default and standalone mode — use them to keep your UI synchronized.
- **Wait for API ready.** Only send commands inside the `apiReadyCallback`. Commands sent before initialization are silently ignored.
- **Validate IDs from events.** Use the `configuratorDataUpdated` event payload to discover valid component, material, and slot IDs rather than hardcoding them.
- **HTTPS is required.** The iframe URL enforces HTTPS. HTTP URLs will be rejected. Use `localhost` for local development.
- **Pin CDN versions in production.** Replace `@latest` with a specific version to avoid unexpected breaking changes.

---

For more examples and advanced usage, visit
our [documentation site](https://preview.3dsource.com/front-libraries/master/modular-configurator-api-doc/).
