@3dsource/metabox-front-api
Version:
API for Metabox BASIC configurator
1,721 lines (1,390 loc) • 52.7 kB
Markdown
# Metabox Basic Configurator API
A powerful TypeScript API for seamlessly integrating and controlling the Metabox Basic Configurator in your
web applications. This API provides full programmatic control over 3D product visualization, products,
their material slots, and materials, environments.
## Table of Contents
- [Features](#features)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [API Reference](#api-reference)
- [Core Functions](#core-functions)
- [Command Classes](#command-classes)
- [Event Handling](#event-handling)
- [Utility Functions](#utility-functions)
- [Examples](#examples)
- [Best Practices](#best-practices)
- [Troubleshooting](#troubleshooting)
- [Browser Compatibility](#browser-compatibility)
- [Contributing](#contributing)
## Features
- 🚀 Easy integration with any web framework (Angular, React, Vue, etc.)
- 📱 Responsive design support
- 🎨 Full control over materials, environments, and products
- 📸 Screenshot and PDF generation
- 🎬 Showcase management
- 📦 TypeScript support with full type definitions
- 🌐 CDN support for quick prototyping
## Prerequisites
- Modern web browser with ES6 module support
- HTTPS connection (required for unreal engine)
- Valid Metabox basic configurator URL
## Installation
### Method 1: NPM Package (Recommended)
For use with frameworks like Angular, React, Vue, etc.:
```bash
npm install @3dsource/metabox-front-api@^1.2.3 --save
```
Then import the required components:
```typescript
import { integrateMetaBox, Communicator, ConfiguratorEnvelope, SetProduct, SetMaterial, SetEnvironment, GetScreenshot, GetPdf, saveImage } from '@3dsource/metabox-front-api';
```
### Method 2: CDN Import
Alternative CDN options:
- **jsDelivr**: `https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api@^1.2.3/+esm`
For quick prototyping or vanilla JavaScript projects:
```javascript
import { integrateMetaBox, Communicator, ConfiguratorEnvelope, SetProduct, SetMaterial, SetEnvironment, GetScreenshot, GetPdf, saveImage } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api';
```
## Quick Start
### 1. HTML Setup
Create a container element where the Metabox 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">
<!-- Metabox Configurator will be embedded here -->
</div>
</body>
</html>
```
### 2. Basic Integration
#### JavaScript Example
```html
<script type="module">
import { integrateMetaBox, SetProduct, SetMaterial, SetEnvironment, GetScreenshot, saveImage } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api@1.2.3';
class MetaboxIntegrator {
constructor() {
// Replace 'your-configurator-id' with your actual basic configurator ID
const configuratorUrl = 'https://metabox.3dsource.com/metabox-configurator/basic/your-configurator-id?sidebar=false';
// Initialize the configurator
integrateMetaBox(configuratorUrl, 'embed3DSource', (api) => {
this.onApiReady(api);
});
}
onApiReady(api) {
console.log('Metabox API is ready!');
// Listen for configurator data updates
api.addEventListener('basicConfiguratorDataWithState', (data) => {
console.log('Configurator data updated:', data);
this.handleConfiguratorUpdate(data);
});
// Listen for screenshot/render completion
api.addEventListener('fastRender', (imageData) => {
console.log('Screenshot ready');
saveImage(imageData, 'configurator-screenshot.png');
});
// Set up the initial configuration
this.configureInitialState(api);
}
handleConfiguratorUpdate(data) {
// Handle configurator state changes
// You can update your UI, save state, etc.
}
configureInitialState(api) {
// Set the initial product
api.sendCommandToMetaBox(new SetProduct('your-product-id'));
// Set initial environment
api.sendCommandToMetaBox(new SetEnvironment('your-environment-id'));
// Apply material to a specific slot
api.sendCommandToMetaBox(new SetMaterial('slot-id', 'material-id'));
// Take a screenshot
api.sendCommandToMetaBox(new GetScreenshot('image/png', { x: 1024, y: 1024 }));
}
}
// Initialize the integrator
new MetaboxIntegrator();
</script>
```
#### TypeScript Example
```typescript
import { integrateMetaBox, Communicator, ConfiguratorEnvelope, SetProduct, SetMaterial, SetEnvironment, GetScreenshot, GetPdf, ShowEmbeddedMenu, ShowOverlayInterface, saveImage } from '@3dsource/metabox-front-api';
class MetaboxIntegrator {
private api: Communicator | null = null;
constructor() {
this.initialize();
}
private async initialize(): Promise<void> {
try {
// Replace 'your-configurator-id' with your actual configurator ID
const configuratorUrl = 'https://metabox.3dsource.com/metabox-configurator/basic/your-configurator-id?sidebar=false';
// Initialize the configurator with type safety
integrateMetaBox(configuratorUrl, 'embed3DSource', (api: Communicator) => {
this.onApiReady(api);
});
} catch (error) {
console.error('Failed to initialize Metabox:', error);
}
}
private onApiReady(api: Communicator): void {
this.api = api;
console.log('Metabox API is ready!');
// Set up event listeners with proper typing
this.setupEventListeners();
// Configure initial state
this.configureInitialState();
}
private setupEventListeners(): void {
if (!this.api) return;
// Listen for configurator data updates
this.api.addEventListener('basicConfiguratorDataWithState', (data: ConfiguratorEnvelope) => {
console.log('Configurator data updated:', data);
this.handleConfiguratorUpdate(data);
});
// Listen for screenshot/render completion
this.api.addEventListener('fastRender', (imageData: string) => {
console.log('Screenshot ready');
saveImage(imageData, 'configurator-screenshot.png');
});
}
private handleConfiguratorUpdate(data: ConfiguratorEnvelope): void {
// Handle configurator state changes with full type safety
// Access data properties like data.product, data.materials, etc.
// Example: Update UI based on current product
if (data.product) {
console.log('Current product:', data.product);
}
}
private configureInitialState(): void {
if (!this.api) return;
// Set initial product
this.api.sendCommandToMetaBox(new SetProduct('your-product-id'));
// Set initial environment
this.api.sendCommandToMetaBox(new SetEnvironment('your-environment-id'));
// Apply material to a specific slot
this.api.sendCommandToMetaBox(new SetMaterial('slot-id', 'material-id'));
// Configure UI visibility
this.api.sendCommandToMetaBox(new ShowEmbeddedMenu(false));
this.api.sendCommandToMetaBox(new ShowOverlayInterface(false));
// Generate initial screenshot
this.api.sendCommandToMetaBox(new GetScreenshot('image/png', { x: 1024, y: 1024 }));
}
// Public method to change product
public changeProduct(productId: string): void {
if (this.api) {
this.api.sendCommandToMetaBox(new SetProduct(productId));
}
}
// Public method to apply material
public applyMaterial(slotId: string, materialId: string): void {
if (this.api) {
this.api.sendCommandToMetaBox(new SetMaterial(slotId, materialId));
}
}
// Public method to take screenshot
public takeScreenshot(width: number = 1024, height: number = 1024): void {
if (this.api) {
this.api.sendCommandToMetaBox(new GetScreenshot('image/png', { x: width, y: height }));
}
}
}
// Initialize the integrator
const metaboxIntegrator = new MetaboxIntegrator();
// Example usage:
// metaboxIntegrator.changeProduct('new-product-id');
// metaboxIntegrator.applyMaterial('slot-1', 'material-2');
// metaboxIntegrator.takeScreenshot(1920, 1080);
```
## API Reference
### Core Functions
#### `integrateMetaBox`
Integrates the Metabox configurator into the specified container element.
**TypeScript Signature:**
```typescript
function integrateMetaBox(configuratorUrl: string, containerId: string, apiReadyCallback: (api: Communicator) => void): void;
```
**Parameters:**
- `configuratorUrl` (string): The URL of your Metabox basic configurator
- `containerId` (string): The ID of the HTML element where the configurator will be embedded
- `apiReadyCallback` (function): Callback function that receives the Communicator API instance
**Example:**
```typescript
integrateMetaBox('https://metabox.3dsource.com/metabox-configurator/basic/your-id', 'embed3DSource', (api) => {
console.log('API ready!', api);
});
```
### Command Classes
All commands are sent to the configurator using the `api.sendCommandToMetaBox()` method.
#### Product Configuration
##### `SetProduct`
Sets the active product by ID.
```typescript
api.sendCommandToMetaBox(new SetProduct(productId));
```
##### `SetMaterial`
Applies a material to a specific product slot.
```typescript
api.sendCommandToMetaBox(new SetMaterial(slotId, materialId));
```
#### Environment Configuration
##### `SetEnvironment`
Changes the environment/background by ID.
```typescript
api.sendCommandToMetaBox(new SetEnvironment(environmentId));
```
##### `SetEnvironmentMaterial`
Applies a material to a specific environment slot.
```typescript
api.sendCommandToMetaBox(new SetEnvironmentMaterial(slotId, materialId));
```
#### Camera Controls
##### `ResetCamera`
Resets the camera to its default position and rotation.
```typescript
api.sendCommandToMetaBox(new ResetCamera());
```
##### `ApplyZoom`
Applies zoom to the camera view.
```typescript
api.sendCommandToMetaBox(new ApplyZoom(zoom));
```
#### Interface Controls
##### `ShowEmbeddedMenu`
Toggles the visibility of the embedded menu in the configurator.
```typescript
api.sendCommandToMetaBox(new ShowEmbeddedMenu(visible));
```
##### `ShowOverlayInterface`
Toggles the overlay interface over the Unreal viewport.
```typescript
api.sendCommandToMetaBox(new ShowOverlayInterface(visible));
```
#### Media Generation
##### `GetScreenshot`
Captures a screenshot/render of the current view.
```typescript
api.sendCommandToMetaBox(new GetScreenshot(mimeType, { x: 1920, y: 1080 }));
```
**Parameters:**
- `mimeType`: Image format ('image/png', 'image/jpeg', etc.)
- `size`: Optional dimensions object with x and y properties
##### `GetFastRender`
Captures a fast render of the current view (optimized for speed).
```typescript
api.sendCommandToMetaBox(new GetFastRender());
```
##### `GetPdf`
Generates a PDF of the current view.
```typescript
api.sendCommandToMetaBox(new GetPdf());
```
#### Showcase Management
##### `InitShowcase`
Initializes a showcase with the given showcase ID.
```typescript
api.sendCommandToMetaBox(new InitShowcase(showcaseId));
```
##### `PlayShowcase`
Plays the currently loaded showcase.
```typescript
api.sendCommandToMetaBox(new PlayShowcase());
```
##### `PauseShowcase`
Pauses the currently playing showcase.
```typescript
api.sendCommandToMetaBox(new PauseShowcase());
```
##### `StopShowcase`
Stops the currently playing showcase and returns to the initial state.
```typescript
api.sendCommandToMetaBox(new StopShowcase());
```
### Event Handling
The API uses an event-driven architecture. Listen for events using the `addEventListener` method:
#### Available Events
##### `basicConfiguratorDataWithState`
Fired when the configurator state changes (product, materials, environment, etc.).
```typescript
api.addEventListener('basicConfiguratorDataWithState', (data: ConfiguratorEnvelope) => {
console.log('Configurator state updated:', data);
// Access data.product, data.materials, data.environment, etc.
});
```
##### `fastRender`
Fired when a screenshot or fast render is completed.
```typescript
api.addEventListener('fastRender', (imageData: string) => {
console.log('Render completed');
// imageData contains the base64 encoded image
saveImage(imageData, 'screenshot.png');
});
```
##### `pdfGenerated`
Fired when PDF generation is completed.
```typescript
api.addEventListener('pdfGenerated', (pdfData: string) => {
console.log('PDF generated');
// Handle PDF data
});
```
### Utility Functions
#### `saveImage`
Utility function to save rendered images to a file.
```typescript
function saveImage(imageUrl: string, filename: string): void;
```
**Parameters:**
- `imageUrl`: The URL of the image to save (can be base64 encoded image data)
- `filename`: The name of the file to save
**Example:**
```typescript
api.addEventListener('fastRender', (imageData: string | null) => {
saveImage(imageData ?? '', 'my-render.png');
});
```
## Examples
### Basic Product Configurator
A complete HTML example showing how to integrate the Metabox configurator with vanilla JavaScript.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Product Configurator</title>
<style>
#metabox-container {
width: 100%;
height: 600px;
border: 1px solid #ddd;
border-radius: 8px;
}
.controls {
margin: 20px 0;
}
button {
margin: 5px;
padding: 10px 15px;
border: 1px solid #ccc;
border-radius: 4px;
background: #f5f5f5;
cursor: pointer;
}
button:hover {
background: #e5e5e5;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.loading {
text-align: center;
padding: 20px;
color: #666;
}
.error {
color: #d32f2f;
padding: 10px;
background: #ffebee;
border: 1px solid #ffcdd2;
border-radius: 4px;
margin: 10px 0;
}
</style>
</head>
<body>
<div id="loading" class="loading">Loading configurator...</div>
<div id="error" class="error" style="display: none;"></div>
<div id="metabox-container"></div>
<div class="controls">
<button id="product1-btn" onclick="changeProduct('product-1')" disabled>Product 1</button>
<button id="product2-btn" onclick="changeProduct('product-2')" disabled>Product 2</button>
<button id="red-material-btn" onclick="applyMaterial('slot-1', 'material-red')" disabled>Red Material</button>
<button id="blue-material-btn" onclick="applyMaterial('slot-1', 'material-blue')" disabled>Blue Material</button>
<button id="screenshot-btn" onclick="takeScreenshot()" disabled>Take Screenshot</button>
</div>
<script type="module">
import { integrateMetaBox, SetProduct, SetMaterial, GetScreenshot, saveImage } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api@latest';
let api = null;
let isApiReady = false;
// Helper function to show/hide loading state
function setLoadingState(loading) {
const loadingEl = document.getElementById('loading');
const buttons = document.querySelectorAll('button');
loadingEl.style.display = loading ? 'block' : 'none';
buttons.forEach((btn) => (btn.disabled = loading || !isApiReady));
}
// Helper function to show error messages
function showError(message) {
const errorEl = document.getElementById('error');
errorEl.textContent = message;
errorEl.style.display = 'block';
setLoadingState(false);
}
// Initialize configurator with error handling
try {
// Replace 'your-configurator-id' with your actual configurator ID from 3DSource
const configuratorUrl = 'https://metabox.3dsource.com/metabox-configurator/basic/your-configurator-id';
integrateMetaBox(configuratorUrl, 'metabox-container', (apiInstance) => {
try {
api = apiInstance;
isApiReady = true;
setLoadingState(false);
console.log('Configurator ready!');
// Listen for configurator state changes
api.addEventListener('basicConfiguratorDataWithState', (data) => {
console.log('Configurator state updated:', data);
// You can update your UI based on the current state
// For example, update available materials, products, etc.
});
// Listen for screenshot completion
api.addEventListener('fastRender', (imageData) => {
console.log('Screenshot captured successfully');
// Save the image with a timestamp
saveImage(imageData, `configurator-screenshot-${Date.now()}.png`);
});
// Listen for errors
api.addEventListener('error', (error) => {
console.error('Configurator error:', error);
showError('An error occurred in the configurator: ' + error.message);
});
} catch (error) {
console.error('Error setting up API:', error);
showError('Failed to initialize configurator API: ' + error.message);
}
});
} catch (error) {
console.error('Error initializing configurator:', error);
showError('Failed to load configurator: ' + error.message);
}
// Global functions for button interactions
window.changeProduct = (productId) => {
if (!isApiReady || !api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Changing to product: ${productId}`);
api.sendCommandToMetaBox(new SetProduct(productId));
} catch (error) {
console.error('Error changing product:', error);
showError('Failed to change product: ' + error.message);
}
};
window.applyMaterial = (slotId, materialId) => {
if (!isApiReady || !api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Applying material ${materialId} to slot ${slotId}`);
api.sendCommandToMetaBox(new SetMaterial(slotId, materialId));
} catch (error) {
console.error('Error applying material:', error);
showError('Failed to apply material: ' + error.message);
}
};
window.takeScreenshot = () => {
if (!isApiReady || !api) {
console.warn('API not ready yet');
return;
}
try {
console.log('Taking screenshot...');
// Request high-quality screenshot in PNG format
api.sendCommandToMetaBox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
} catch (error) {
console.error('Error taking screenshot:', error);
showError('Failed to take screenshot: ' + error.message);
}
};
</script>
</body>
</html>
```
**Key Features of this Example:**
- **Error Handling**: Comprehensive error handling for all API operations
- **Loading States**: Visual feedback while the configurator loads
- **Button States**: Buttons are disabled until the API is ready
- **Console Logging**: Detailed logging for debugging
- **Responsive Design**: Clean, responsive button layout
- **Latest CDN**: Uses `@latest` for the most recent version
**To Use This Example:**
1. Replace `'your-configurator-id'` with your actual configurator ID
2. Replace placeholder product IDs (`'product-1'`, `'product-2'`) with real product IDs
3. Replace placeholder material IDs (`'material-red'`, `'material-blue'`) with real material IDs
4. Replace placeholder slot ID (`'slot-1'`) with the actual slot ID from your configurator
### React Integration Example
A complete React component with TypeScript showing proper integration, error handling, and cleanup.
```tsx
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { integrateMetaBox, Communicator, SetProduct, SetMaterial, GetScreenshot, saveImage, ConfiguratorEnvelope } from '@3dsource/metabox-front-api';
interface MetaboxConfiguratorProps {
configuratorUrl: string;
onStateChange?: (data: ConfiguratorEnvelope) => void;
onError?: (error: string) => void;
className?: string;
}
interface ConfiguratorState {
isLoading: boolean;
error: string | null;
isApiReady: boolean;
}
const MetaboxConfigurator: React.FC<MetaboxConfiguratorProps> = ({ configuratorUrl, onStateChange, onError, className }) => {
const containerRef = useRef<HTMLDivElement>(null);
const [api, setApi] = useState<Communicator | null>(null);
const [state, setState] = useState<ConfiguratorState>({
isLoading: true,
error: null,
isApiReady: false,
});
// Generate unique container ID to avoid conflicts
const containerId = useRef(`metabox-container-${Math.random().toString(36).substr(2, 9)}`);
// Error handler
const handleError = useCallback(
(error: string) => {
setState((prev) => ({ ...prev, error, isLoading: false }));
onError?.(error);
console.error('Metabox Configurator Error:', error);
},
[onError],
);
// Initialize configurator
useEffect(() => {
if (!containerRef.current) return;
let mounted = true;
const initializeConfigurator = async () => {
try {
setState((prev) => ({ ...prev, isLoading: true, error: null }));
// Set the container ID
containerRef.current!.id = containerId.current;
integrateMetaBox(configuratorUrl, containerId.current, (apiInstance) => {
if (!mounted) return; // Component was unmounted
try {
setApi(apiInstance);
setState((prev) => ({ ...prev, isLoading: false, isApiReady: true }));
// Set up event listeners with proper typing
apiInstance.addEventListener('basicConfiguratorDataWithState', (data: ConfiguratorEnvelope) => {
if (mounted) {
onStateChange?.(data);
}
});
apiInstance.addEventListener('fastRender', (imageData: string) => {
if (mounted) {
console.log('Screenshot captured successfully');
saveImage(imageData, `configurator-screenshot-${Date.now()}.png`);
}
});
// Listen for errors from the configurator
apiInstance.addEventListener('error', (error: any) => {
if (mounted) {
handleError(`Configurator error: ${error.message || error}`);
}
});
console.log('React Metabox Configurator initialized successfully');
} catch (error) {
if (mounted) {
handleError(`Failed to set up API: ${error instanceof Error ? error.message : String(error)}`);
}
}
});
} catch (error) {
if (mounted) {
handleError(`Failed to initialize configurator: ${error instanceof Error ? error.message : String(error)}`);
}
}
};
initializeConfigurator();
// Cleanup function
return () => {
mounted = false;
if (api) {
// Clean up any event listeners if the API provides cleanup methods
console.log('Cleaning up Metabox Configurator');
}
};
}, [configuratorUrl, onStateChange, handleError, api]);
// Command methods with error handling
const changeProduct = useCallback(
(productId: string) => {
if (!state.isApiReady || !api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Changing to product: ${productId}`);
api.sendCommandToMetaBox(new SetProduct(productId));
} catch (error) {
handleError(`Failed to change product: ${error instanceof Error ? error.message : String(error)}`);
}
},
[api, state.isApiReady, handleError],
);
const applyMaterial = useCallback(
(slotId: string, materialId: string) => {
if (!state.isApiReady || !api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Applying material ${materialId} to slot ${slotId}`);
api.sendCommandToMetaBox(new SetMaterial(slotId, materialId));
} catch (error) {
handleError(`Failed to apply material: ${error instanceof Error ? error.message : String(error)}`);
}
},
[api, state.isApiReady, handleError],
);
const takeScreenshot = useCallback(() => {
if (!state.isApiReady || !api) {
console.warn('API not ready yet');
return;
}
try {
console.log('Taking screenshot...');
api.sendCommandToMetaBox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
} catch (error) {
handleError(`Failed to take screenshot: ${error instanceof Error ? error.message : String(error)}`);
}
}, [api, state.isApiReady, handleError]);
// Render error state
if (state.error) {
return (
<div className={className}>
<div
style={{
color: '#d32f2f',
padding: '20px',
background: '#ffebee',
border: '1px solid #ffcdd2',
borderRadius: '4px',
textAlign: 'center',
}}
>
<h3>Configurator Error</h3>
<p>{state.error}</p>
<button
onClick={() => window.location.reload()}
style={{
padding: '10px 20px',
marginTop: '10px',
border: '1px solid #d32f2f',
background: '#fff',
color: '#d32f2f',
borderRadius: '4px',
cursor: 'pointer',
}}
>
Reload Page
</button>
</div>
</div>
);
}
return (
<div className={className}>
{/* Loading indicator */}
{state.isLoading && (
<div
style={{
textAlign: 'center',
padding: '40px',
color: '#666',
background: '#f5f5f5',
borderRadius: '8px',
}}
>
<div>Loading configurator...</div>
<div style={{ marginTop: '10px', fontSize: '14px' }}>Please wait while we initialize the 3D viewer</div>
</div>
)}
{/* Configurator container */}
<div
ref={containerRef}
style={{
width: '100%',
height: '600px',
border: '1px solid #ddd',
borderRadius: '8px',
display: state.isLoading ? 'none' : 'block',
}}
/>
{/* Controls */}
{state.isApiReady && (
<div style={{ marginTop: '20px' }}>
<div style={{ marginBottom: '10px', fontWeight: 'bold', color: '#333' }}>Configurator Controls:</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px' }}>
<button
onClick={() => changeProduct('product-1')}
disabled={!state.isApiReady}
style={{
padding: '10px 15px',
border: '1px solid #ccc',
borderRadius: '4px',
background: state.isApiReady ? '#f5f5f5' : '#e0e0e0',
cursor: state.isApiReady ? 'pointer' : 'not-allowed',
transition: 'background-color 0.2s',
}}
onMouseOver={(e) => {
if (state.isApiReady) e.currentTarget.style.background = '#e5e5e5';
}}
onMouseOut={(e) => {
if (state.isApiReady) e.currentTarget.style.background = '#f5f5f5';
}}
>
Product 1
</button>
<button
onClick={() => changeProduct('product-2')}
disabled={!state.isApiReady}
style={{
padding: '10px 15px',
border: '1px solid #ccc',
borderRadius: '4px',
background: state.isApiReady ? '#f5f5f5' : '#e0e0e0',
cursor: state.isApiReady ? 'pointer' : 'not-allowed',
}}
>
Product 2
</button>
<button
onClick={() => applyMaterial('slot-1', 'material-red')}
disabled={!state.isApiReady}
style={{
padding: '10px 15px',
border: '1px solid #ccc',
borderRadius: '4px',
background: state.isApiReady ? '#f5f5f5' : '#e0e0e0',
cursor: state.isApiReady ? 'pointer' : 'not-allowed',
}}
>
Red Material
</button>
<button
onClick={() => applyMaterial('slot-1', 'material-blue')}
disabled={!state.isApiReady}
style={{
padding: '10px 15px',
border: '1px solid #ccc',
borderRadius: '4px',
background: state.isApiReady ? '#f5f5f5' : '#e0e0e0',
cursor: state.isApiReady ? 'pointer' : 'not-allowed',
}}
>
Blue Material
</button>
<button
onClick={takeScreenshot}
disabled={!state.isApiReady}
style={{
padding: '10px 15px',
border: '1px solid #007bff',
borderRadius: '4px',
background: state.isApiReady ? '#007bff' : '#6c757d',
color: 'white',
cursor: state.isApiReady ? 'pointer' : 'not-allowed',
}}
>
📸 Take Screenshot
</button>
</div>
</div>
)}
</div>
);
};
export default MetaboxConfigurator;
```
**Key Features of this React Example:**
- **Proper TypeScript Integration**: Full type safety with proper interfaces
- **Error Handling**: Comprehensive error states and user feedback
- **Component Cleanup**: Proper unmounting and memory leak prevention
- **Unique Container IDs**: Automatic ID generation to avoid conflicts
- **Loading States**: Visual feedback during initialization
- **Optimized Re-renders**: Uses useCallback to prevent unnecessary re-renders
- **Responsive Design**: Flexible button layout with proper styling
**Usage Example:**
```tsx
// App.tsx
import React from 'react';
import MetaboxConfigurator from './components/MetaboxConfigurator';
import { ConfiguratorEnvelope } from '@3dsource/metabox-front-api';
function App() {
const handleStateChange = (data: ConfiguratorEnvelope) => {
console.log('Configurator state changed:', data);
// Update your application state based on configurator changes
};
const handleError = (error: string) => {
console.error('Configurator error:', error);
// Handle errors (show notifications, log to analytics, etc.)
};
return (
<div className="App">
<h1>My Product Configurator</h1>
<MetaboxConfigurator configuratorUrl="https://metabox.3dsource.com/metabox-configurator/basic/your-configurator-id" onStateChange={handleStateChange} onError={handleError} className="my-configurator" />
</div>
);
}
export default App;
```
**To Use This Component:**
1. Install the package: `npm install @3dsource/metabox-front-api`
2. Replace `'your-configurator-id'` with your actual configurator ID
3. Replace placeholder IDs in the component with your real product, material, and slot IDs
4. Customize the styling by passing a `className` prop or modifying the inline styles
### Angular Integration Example
A complete Angular component with TypeScript showing proper integration, error handling, and lifecycle management.
```typescript
// metabox-configurator.component.ts
import { Component, input, OnInit, output, signal } from '@angular/core';
import { Communicator, ConfiguratorEnvelope, GetPdf, GetScreenshot, InitShowcase, integrateMetaBox, PauseShowcase, PlayShowcase, saveImage, SetMaterial, SetProduct, ShowEmbeddedMenu, ShowOverlayInterface, StopShowcase } from '@3dsource/metabox-front-api';
interface ConfiguratorState {
isLoading: boolean;
error: string | null;
isApiReady: boolean;
}
@Component({
selector: 'app-metabox-configurator',
template: `
@let _state = state();
<div class="configurator-container">
<!-- Loading State -->
@if (_state.isLoading) {
<div class="loading-container">
<div class="loading-content">
<div class="loading-spinner"></div>
<div class="loading-text">Loading configurator...</div>
<div class="loading-subtext">Please wait while we initialize the 3D viewer</div>
</div>
</div>
}
<!-- Error State -->
@if (_state.error) {
<div class="error-container">
<h3>Configurator Error</h3>
<p>{{ _state.error }}</p>
<button (click)="retryInitialization()" class="retry-button">Retry</button>
</div>
}
<!-- Configurator Container -->
<div [id]="containerId()" class="configurator-viewport" [hidden]="_state.isLoading || _state.error"></div>
<!-- Controls -->
@if (_state.isApiReady) {
<div class="controls">
<div class="controls-title">Configurator Controls:</div>
<div class="controls-buttons">
<button (click)="changeProduct('541f46ab-a86c-48e3-bcfa-f92341483db3')" [disabled]="!_state.isApiReady" class="control-button">Product Change</button>
<button (click)="initShowcase('685e656f-32e4-4a63-a9f3-bf3c9e440906')" [disabled]="!_state.isApiReady" class="control-button">Init showcase</button>
<button (click)="stopShowcase()" [disabled]="!_state.isApiReady" class="control-button">Stop showcase</button>
<button (click)="playShowcase()" [disabled]="!_state.isApiReady" class="control-button">Play showcase</button>
<button (click)="pauseShowcase()" [disabled]="!_state.isApiReady" class="control-button">Pause showcase</button>
<button (click)="applyMaterial('slot-1', 'material-red')" [disabled]="!_state.isApiReady" class="control-button">Red Material</button>
<button (click)="applyMaterial('slot-1', 'material-blue')" [disabled]="!_state.isApiReady" class="control-button">Blue Material</button>
<button (click)="getPdf()" [disabled]="!_state.isApiReady" class="control-button">Get PDF</button>
<button (click)="takeScreenshot()" [disabled]="!_state.isApiReady" class="control-button screenshot-button">📸 Take Screenshot</button>
</div>
</div>
}
</div>
`,
styles: [
`
.configurator-container {
width: 100%;
position: relative;
}
.configurator-viewport {
width: 100%;
height: 600px;
border: 1px solid #ddd;
border-radius: 8px;
}
.loading-container {
display: flex;
align-items: center;
justify-content: center;
height: 600px;
background: #f5f5f5;
border: 1px solid #ddd;
border-radius: 8px;
}
.loading-content {
text-align: center;
color: #666;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.loading-text {
font-size: 16px;
font-weight: 500;
margin-bottom: 8px;
}
.loading-subtext {
font-size: 14px;
opacity: 0.8;
}
.error-container {
padding: 40px;
text-align: center;
background: #ffebee;
border: 1px solid #ffcdd2;
border-radius: 8px;
color: #d32f2f;
}
.error-container h3 {
margin: 0 0 16px 0;
font-size: 18px;
}
.error-container p {
margin: 0 0 20px 0;
font-size: 14px;
}
.retry-button {
padding: 10px 20px;
border: 1px solid #d32f2f;
background: #fff;
color: #d32f2f;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.retry-button:hover {
background: #d32f2f;
color: #fff;
}
.controls {
margin-top: 20px;
}
.controls-title {
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.controls-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.control-button {
padding: 10px 15px;
border: 1px solid #ccc;
border-radius: 4px;
background: #f5f5f5;
cursor: pointer;
transition: all 0.2s;
font-size: 14px;
}
.control-button:hover:not(:disabled) {
background: #e5e5e5;
}
.control-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.screenshot-button {
border-color: #007bff;
background: #007bff;
color: white;
}
.screenshot-button:hover:not(:disabled) {
background: #0056b3;
}
`,
],
})
export class MetaboxConfiguratorComponent implements OnInit {
configuratorUrl = input.required<string>();
stateChange = output<ConfiguratorEnvelope>();
errorFired = output<string>();
state = signal<ConfiguratorState>({
isLoading: true,
error: null,
isApiReady: false,
});
containerId = signal(`metabox-container-${Math.random().toString(36).substring(2, 9)}`);
private api: Communicator | null = null;
ngOnInit(): void {
this.initializeConfigurator();
}
// Public methods for external control
changeProduct(productId: string): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Changing to product: ${productId}`);
this.api.sendCommandToMetaBox(new SetProduct(productId));
} catch (error) {
this.handleError(`Failed to change product: ${this.getErrorMessage(error)}`);
}
}
initShowcase(productId: string): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Init showcase for product: ${productId}`);
this.api.sendCommandToMetaBox(new InitShowcase(productId));
} catch (error) {
this.handleError(`Failed to init showcase for product: ${this.getErrorMessage(error)}`);
}
}
stopShowcase(): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Stop showcase`);
this.api.sendCommandToMetaBox(new StopShowcase());
} catch (error) {
this.handleError(`Failed to init showcase for product: ${this.getErrorMessage(error)}`);
}
}
playShowcase(): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Play showcase`);
this.api.sendCommandToMetaBox(new PlayShowcase());
} catch {
this.handleError(`Failed to play showcase`);
}
}
pauseShowcase(): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Pause showcase`);
this.api.sendCommandToMetaBox(new PauseShowcase());
} catch {
this.handleError(`Failed to pause showcase`);
}
}
applyMaterial(slotId: string, materialId: string): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Applying material ${materialId} to slot ${slotId}`);
this.api.sendCommandToMetaBox(new SetMaterial(slotId, materialId));
} catch (error) {
this.handleError(`Failed to apply material: ${this.getErrorMessage(error)}`);
}
}
takeScreenshot(): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log('Taking screenshot...');
this.api.sendCommandToMetaBox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
} catch (error) {
this.handleError(`Failed to take screenshot: ${this.getErrorMessage(error)}`);
}
}
getPdf(): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log('Generating PDF...');
this.api.sendCommandToMetaBox(new GetPdf());
} catch (error) {
this.handleError(`Failed to generating pdf: ${this.getErrorMessage(error)}`);
}
}
retryInitialization(): void {
this.updateState({ error: null });
this.initializeConfigurator();
}
sendInitCommands() {
if (!this.api) {
return;
}
this.api.sendCommandToMetaBox(new ShowEmbeddedMenu(true));
this.api.sendCommandToMetaBox(new ShowOverlayInterface(true));
}
private initializeConfigurator() {
try {
this.updateState({ isLoading: true, error: null });
integrateMetaBox(this.configuratorUrl(), this.containerId(), (apiInstance) => {
try {
this.api = apiInstance;
this.updateState({ isLoading: false, isApiReady: true });
this.sendInitCommands();
this.setupEventListeners();
console.log('Angular Metabox Configurator initialized successfully');
} catch (error) {
this.handleError(`Failed to set up API: ${this.getErrorMessage(error)}`);
}
});
} catch (error) {
this.handleError(`Failed to initialize configurator: ${this.getErrorMessage(error)}`);
}
}
private setupEventListeners(): void {
if (!this.api) {
return;
}
// Listen for configurator state changes
this.api.addEventListener('basicConfiguratorDataWithState', (data: ConfiguratorEnvelope) => {
console.log('Configurator state updated:', data);
this.stateChange.emit(data);
});
// Listen for screenshot completion
this.api.addEventListener('screenshot', (imageData: string | null) => {
console.log(`Screenshot captured successfully ${imageData ?? ''}`);
saveImage(imageData ?? '', `configurator-screenshot-${Date.now()}.png`);
});
}
private updateState(updates: Partial<ConfiguratorState>): void {
this.state.set({ ...this.state(), ...updates });
}
private handleError(errorMessage: string): void {
console.error('Metabox Configurator Error:', errorMessage);
this.updateState({ error: errorMessage, isLoading: false });
this.errorFired.emit(errorMessage);
}
private getErrorMessage(error: any): string {
if (error instanceof Error) {
return error.message;
}
return String(error);
}
}
```
**Key Features of this Angular Example:**
- **Comprehensive Error Handling**: Try-catch blocks and user-friendly error states
- **Proper Lifecycle Management**: OnDestroy implementation with cleanup
- **Loading States**: Visual feedback with spinner during initialization
- **TypeScript Integration**: Full type safety with proper interfaces
- **Unique Container IDs**: Automatic ID generation to avoid conflicts
- **Event Outputs**: Emits state changes and errors to parent components
- **Responsive Design**: Clean, accessible button layout with proper styling
- **Component Cleanup**: Prevents memory leaks and handles component destruction
**Usage Example:**
```typescript
// app.component.ts
import { Component } from '@angular/core';
import { ConfiguratorEnvelope } from '@3dsource/metabox-front-api';
@Component({
selector: 'app-root',
template: `
<div class="app-container">
<h1>My Product Configurator</h1>
<app-metabox-configurator [configuratorUrl]="configuratorUrl" (stateChange)="onConfiguratorStateChange($event)" (errorFired)="onConfiguratorError($event)"></app-metabox-configurator>
</div>
`,
styles: [
`
.app-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
`,
],
})
export class AppComponent {
configuratorUrl = 'https://metabox.3dsource.com/metabox-configurator/basic/your-configurator-id';
onConfiguratorStateChange(data: ConfiguratorEnvelope): void {
console.log('Configurator state changed:', data);
// Update your application state based on configurator changes
}
onConfiguratorError(error: string): void {
console.error('Configurator error:', error);
// Handle errors (show notifications, log to analytics, etc.)
}
}
```
**To Use This Component:**
1. Install the package: `npm install @3dsource/metabox-front-api`
2. Import the component in your Angular module
3. Replace `'your-configurator-id'` with your actual configurator ID
4. Replace placeholder IDs with your real product, material, and slot IDs
5. Customize the styling by modifying the component styles
## Best Practices
### 1. Error Handling
Always implement proper error handling when working with the API:
```typescript
integrateMetaBox(configuratorUrl, containerId, (api) => {
try {
// Set up event listeners with error handling
api.addEventListener('basicConfiguratorDataWithState', (data) => {
try {
handleConfiguratorUpdate(data);
} catch (error) {
console.error('Error handling configurator update:', error);
}
});
// Send commands with error handling
api.sendCommandToMetaBox(new SetProduct(productId));
} catch (error) {
console.error('Error initializing configurator:', error);
}
});
```
### 2. State Management
Keep track of the configurator state for a better user experience:
```typescript
class ConfiguratorStateManager {
private currentState = {
product: null,
materials: {},
environment: null,
};
updateState(data) {
this.currentState = { ...this.currentState, ...data };
this.saveStateToLocalStorage();
}
saveStateToLocalStorage() {
localStorage.setItem('configuratorState', JSON.stringify(this.currentState));
}
loadStateFromLocalStorage() {
const saved = localStorage.getItem('configuratorState');
return saved ? JSON.parse(saved) : null;
}
}
```
### 3. Performance Optimization
- **Debounce rapid commands**: Avoid sending too many commands in quick succession
```typescript
// Debounce example
const debouncedMaterialChange = debounce((slotId, materialId) => {
api.sendCommandToMetaBox(new SetMaterial(slotId, materialId));
}, 300);
```
### 4. Responsive Design
Ensure the configurator works well on different screen sizes:
```css
#embed3DSource {
width: 100%;
height: 60vh;
min-height: 400px;
max-height: 800px;
}
@media (max-width: 768px) {
#embed3DSource {
height: 50vh;
min-height: 300px;
}
}
```
### 5. Loading States
Provide feedback to users while the configurator loads:
```typescript
const showLoadingState = () => {
document.getElementById('loading').style.display = 'block';
};
const hideLoadingState = () => {
document.getElementById('loading').style.display = 'none';
};
integrateMetaBox(configuratorUrl, containerId, (api) => {
hideLoadingState();
// Continue with initialization
});
```
## Troubleshooting
### Common Issues
#### 1. Configurator Not Loading
**Problem**: The configurator iframe doesn't appear or shows a blank screen.
**Solutions**:
- Verify the configurator URL is correct and accessible
- Ensure the container element exists in the DOM before calling `integrateMetaBox`
- Check that you're using HTTPS (required for Unreal Engine)
- Verify your configurator ID is valid
```typescript
// Check if container exists
const container = document.getElementById('embed3DSource');
if (!container) {
console.error('Container element not found');
return;
}
// Verify HTTPS
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
console.warn('HTTPS is required for Metabox configurator');
}
```
#### 2. Commands Not Working
**Problem**: Commands sent to the configurator don't have any effect.
**Solutions**:
- Ensure the API is fully initialized before sending commands
- Check that product/material/environment IDs are correct
- Verify the configurator supports the specific command
```typescript
let apiReady = false;
integrateMetaBox(configuratorUrl, containerId, (api) => {
apiReady = true;
// Now it's safe to send commands
});
// Wait for API to be ready
const sendCommand = (command) => {
if (apiReady && api) {
api.sendCommandToMetaBox(command);
} else {
console.warn('API not ready yet');
}
};
```
#### 3. Events Not Firing
**Problem**: Event listeners don't receive events from the configurator.
**Solutions**:
- Set up event listeners immediately after API initialization
- Check event names for typos
- Ensure the configurator is configured to send the expected events
```typescript
integrateMetaBox(configuratorUrl, containerId, (api) => {
// Set up listeners immediately
api.addEventListener('basicConfiguratorDataWithState', (data) => {
console.log('Event received:', data);
});
// Test if events are working
setTimeout(() => {
console.log('Testing event system...');
api.sendCommandToMetaBox(new SetProduct('test-product'));
}, 1000);
});
```
#### 4. Screenshot/PDF Generation Issues
**Problem**: Screenshots or PDFs are not generated or are of poor quality.
**Solutions**:
- Wait for th