@3dsource/metabox-modular-configurator-api
Version:
API for Metabox MODULAR configurator
714 lines (688 loc) • 23.7 kB
JavaScript
import { fromEvent } from 'rxjs';
/**
* Base class for commands sent to the Metabox.
*/
class CommandBase {
}
/**
* Wrapper helper function to use it with rxJs the same as `fromEvent` to listen to events from the Communicator.
* @param {Communicator} target
* @param eventName
*/
function fromCommunicatorEvent(target, eventName) {
return fromEvent(target, eventName);
}
const MetaboxModularConfiguratorActions = {
setEnvironment: 'setEnvironment',
setEnvironmentMaterialById: 'setEnvironmentMaterialById',
setComponent: 'setComponent',
setComponentType: 'setComponentType',
setComponentMaterialById: 'setComponentMaterialById',
getPdf: 'getPdf',
getScreenshot: 'getScreenshot',
showEmbeddedMenu: 'showEmbeddedMenu',
forceSetDeviceType: 'forceSetDeviceType',
showOverlayInterface: 'showOverlayInterface',
resetCamera: 'resetCamera',
applyZoom: 'applyZoom',
initShowcase: 'initShowcase',
playShowcase: 'playShowcase',
pauseShowcase: 'pauseShowcase',
stopShowcase: 'stopShowcase',
sendCommandToUnreal: 'sendCommandToUnreal',
};
/**
* Represents a command to get a PDF from Metabox.
* @remarks
* This class sends a message to the Metabox API to generate a PDF based on the current configuration.
* This action does not require any parameters.
*
* **Important**: Invoking this command will buffer it until the viewport is ready. Only then will the request be processed.
*
* @example
* import { GetPdf, Communicator } from '@3dsource/metabox-modular-configurator-api';
* window.env3DSource.apiReady = (api: Communicator) => {
* api.sendCommandToMetaBox(new GetPdf());
* };
*/
class GetPdf extends CommandBase {
/**
* Creates an instance of GetPdf.
*
* @remarks
* This constructor does not require any parameters.
*/
constructor() {
super();
this.data = { action: MetaboxModularConfiguratorActions.getPdf };
}
}
/**
* Represents a command to send to unreal and reset camera to initial.
* @remarks
* This class sends a message to the Metabox API to reset the camera on a scene.
* This action does not require any parameters.
*
* **Important**: Invoking this command will buffer it until the viewport is ready. Only then will the request be processed.
*
* @example
* import { ResetCamera, Communicator } from '@3dsource/metabox-modular-configurator-api';
* window.env3DSource.apiReady = (api: Communicator) => {
* api.sendCommandToMetaBox(new ResetCamera());
* };
*/
class ResetCamera extends CommandBase {
/**
* Creates an instance of ResetCamera.
*
* @remarks
* This constructor does not require any parameters.
*/
constructor() {
super();
this.data = { action: MetaboxModularConfiguratorActions.resetCamera };
}
}
/**
* Represents a command to ApplyZoom and change zoom camera on a scene.
* @remarks
* This class sends a command to the Metabox API to change zoom on a scene.
*
* **Important**: Invoking this command will buffer it until the viewport is ready. Only then will the request be processed.
*
* @example
* import { ApplyZoom, Communicator } from '@3dsource/metabox-modular-configurator-api';
* window.env3DSource.apiReady = (api: Communicator) => {
* api.sendCommandToMetaBox(new ApplyZoom(10));
* api.sendCommandToMetaBox(new ApplyZoom(-10));
* };
*/
class ApplyZoom extends CommandBase {
/**
* Creates an instance of ApplyZoom.
*
* @param {...number} zoom
*/
constructor(zoom) {
super();
this.data = {
action: MetaboxModularConfiguratorActions.applyZoom,
payload: { zoom },
};
}
}
/**
* Represents a command to Init Showcase for a product in a component on a scene and start a sequence for the product.
* @remarks
* This class sends a command to the Metabox API to init showcase if the product in a component has a sequence.
* For check this needs to find a showcase property in current product in a component
* If this property exist you can send init showcase command
* @internal Use {@link UniversalConfiguratorProduct.product.showcase}
* @remarks
* You can send init showcase command only for root components, otherwise we will ignore this command
*
* **Important**: Invoking this command will buffer it until the viewport is ready. Only then will the request be processed.
*
* @example
* import { InitShowcase, Communicator } from '@3dsource/metabox-modular-configurator-api';
* window.env3DSource.apiReady = (api: Communicator) => {
* api.sendCommandToMetaBox(new InitShowcase('ffea6b5c-3a8a-4f56-9417-e605acb5cca3');
* };
*/
class InitShowcase extends CommandBase {
/**
* Creates an instance of InitShowcase.
*
* @param {string} productId
*/
constructor(productId) {
super();
this.data = {
action: MetaboxModularConfiguratorActions.initShowcase,
payload: { productId },
};
}
}
/**
* @internal
* @hidden
* Represents a command to send Unreal command.
*/
class UnrealCommand extends CommandBase {
constructor(payload) {
super();
this.data = {
payload,
action: MetaboxModularConfiguratorActions.sendCommandToUnreal,
};
}
}
/**
* Represents a command to Play Showcase for a product when it already init, and you call pause, for example, before it.
* @remarks
* This class sends a command to the Metabox API to play showcase for a product if it is already initialized and pause.
*
* **Important**: Invoking this command will buffer it until the viewport is ready. Only then will the request be processed.
*
* @example
* import { PlayShowcase, Communicator } from '@3dsource/metabox-modular-configurator-api';
* window.env3DSource.apiReady = (api: Communicator) => {
* api.sendCommandToMetaBox(new PlayShowcase());
* };
*/
class PlayShowcase extends CommandBase {
/**
* Creates an instance of PlayShowcase.
*
* @remarks
* This constructor does not require any parameters.
*/
constructor() {
super();
this.data = { action: MetaboxModularConfiguratorActions.playShowcase };
}
}
/**
* Represents a command to Pause Showcase for a product when it already init and play, and you call pause.
* @remarks
* This class sends a command to the Metabox API to pause showcase for a product if it is already initialized and play.
*
* **Important**: Invoking this command will buffer it until the viewport is ready. Only then will the request be processed.
*
* @example
* import { PauseShowcase, Communicator } from '@3dsource/metabox-modular-configurator-api';
* window.env3DSource.apiReady = (api: Communicator) => {
* api.sendCommandToMetaBox(new PauseShowcase());
* };
*/
class PauseShowcase extends CommandBase {
/**
* Creates an instance of PauseShowcase.
*
* @remarks
* This constructor does not require any parameters.
*/
constructor() {
super();
this.data = { action: MetaboxModularConfiguratorActions.pauseShowcase };
}
}
/**
* Represents a command to Stop Showcase for a product when it already init, and you want to destroy it.
* @remarks
* This class sends a command to the Metabox API to Stop showcase for a product if it is already initialized.
*
* **Important**: Invoking this command will buffer it until the viewport is ready. Only then will the request be processed.
*
* @example
* import { StopShowcase, Communicator } from '@3dsource/metabox-modular-configurator-api';
* window.env3DSource.apiReady = (api: Communicator) => {
* api.sendCommandToMetaBox(new StopShowcase());
* };
*/
class StopShowcase extends CommandBase {
/**
* Creates an instance of StopShowcase.
*
* @remarks
* This constructor does not require any parameters.
*/
constructor() {
super();
this.data = { action: MetaboxModularConfiguratorActions.stopShowcase };
}
}
/**
* Represents a command to get a screenshot.
*
* **Important**: Invoking this command will buffer it until the viewport is ready. Only then will the request be processed.
*
* @example
* import { GetScreenshot, Communicator, saveImage } from '@3dsource/metabox-modular-configurator-api';
* window.env3DSource.apiReady = (api: Communicator) => {
* // Listen for events from to the Metabox API
* api.addEventListener('screenshot', (data) => {
* // Process the get screenshot response
* saveImage(data, 'Render.png');
* });
* api.sendCommandToMetaBox(new GetScreenshot('image/png', { x: 1024, y: 1024 }));
* };
*/
class GetScreenshot extends CommandBase {
/**
* Constructs an instance of GetScreenshot.
* @param {MimeType} mimeType - The output format.
* @param {{ x: number; y: number }} [size] - Optional size in pixels.
*/
constructor(mimeType, size) {
super();
this.data = {
payload: { format: mimeType, size },
action: MetaboxModularConfiguratorActions.getScreenshot,
};
}
}
/**
* Represents a command to set a component by its data (ID, typeId, isRoot).
*
* @remarks
* This action sends a message to the Metabox API to set a component using the provided component ID and its component type data.
*
* @example
* import { SetComponent, Communicator } from '@3dsource/metabox-modular-configurator-api';
*
* //...Assume that the integration is already implemented
* window.env3DSource.apiReady = (api: Communicator) => {
* api.setCommandToMetaBox(new SetComponent(
* 'ffea6b5c-3a8a-4f56-9417-e605acb5cca3', 'typeId', true
* ));
* };
*/
class SetComponent extends CommandBase {
/**
* Creates an instance of SetComponent.
*
* @param {string} id
* @param {string} typeId
* @param {boolean} isRoot
*/
constructor(id, typeId, isRoot) {
super();
this.data = {
payload: { id, typeId, isRoot },
action: MetaboxModularConfiguratorActions.setComponent,
};
}
}
/**
* Represents a command to set component material by its slot ID and Material ID.
*
* @example
* import { SetComponentMaterial, Communicator } from '@3dsource/metabox-modular-configurator-api';
*
* //...Assume that the integration is already implemented
* window.env3DSource.apiReady = (api: Communicator) => {
* api.sendCommandToMetaBox(new SetComponentMaterial(
* 'carpaint',
* 'dd829d6e-9200-47a7-8d5b-af5df89b7e91',
* ));
* };
*/
class SetComponentMaterial extends CommandBase {
/**
* Creates an instance of SetComponentMaterial.
*
* @param {string} slotId - The slot ID.
* @param {string} materialId - The material ID.
*/
constructor(slotId, materialId) {
super();
this.data = {
payload: { slotId, materialId },
action: MetaboxModularConfiguratorActions.setComponentMaterialById,
};
}
}
/**
* Represents a command to set a component type by its ID.
*
* @remarks
* This action sends a message to the Metabox API to set a component type using the provided component type ID.
*
* @example
* import { SetComponentType, Communicator } from '@3dsource/metabox-modular-configurator-api';
*
* //...Assume that the integration is already implemented
* window.env3DSource.apiReady = (api: Communicator) => {
* api.setCommandToMetaBox(new SetComponentType(
* 'ffea6b5c-3a8a-4f56-9417-e605acb5cca3'
* ));
* };
*/
class SetComponentType extends CommandBase {
/**
* Creates an instance of SetComponentType.
*
* @param {string} typeId
*/
constructor(typeId) {
super();
this.data = {
payload: { typeId },
action: MetaboxModularConfiguratorActions.setComponentType,
};
}
}
/**
* Represents a command to set the environment by its ID.
*
* @example
* import { SetEnvironment, Communicator } from '@3dsource/metabox-modular-configurator-api';
*
* //...Assume that the integration is already implemented
* window.env3DSource.apiReady = (api: Communicator) => {
* // Change the environment to '55555555-1234-1234-1234-01234567890'
* api.sendCommandToMetaBox(new SetEnvironment('55555555-1234-1234-1234-01234567890'));
* };
*/
class SetEnvironment extends CommandBase {
/**
* Creates an instance of SetEnvironment.
*
* @param {string} environmentId - The environment ID.
*/
constructor(environmentId) {
super();
this.data = {
payload: { id: environmentId },
action: MetaboxModularConfiguratorActions.setEnvironment,
};
}
}
/**
* Represents a command to set an environment material by its slot ID and Material ID.
*
* @example
* import { SetEnvironmentMaterial, Communicator } from '@3dsource/metabox-modular-configurator-api';
*
* //...Assume that the integration is already implemented
* window.env3DSource.apiReady = (api: Communicator) => {
* api.sendCommandToMetaBox(new SetEnvironmentMaterial(
* 'carpaint',
* 'dd829d6e-9200-47a7-8d5b-af5df89b7e91',
* ));
* };
*/
class SetEnvironmentMaterial extends CommandBase {
/**
* Creates an instance of SetEnvironmentMaterial.
*
* @param {string} slotId - The slot ID.
* @param {string} materialId - The material ID.
*/
constructor(slotId, materialId) {
super();
this.data = {
payload: { slotId, materialId },
action: MetaboxModularConfiguratorActions.setEnvironmentMaterialById,
};
}
}
/**
* Represents a command to toggle the Embedded to the Metabox Menu.
*
* @example
* import { ShowEmbeddedMenu, Communicator } from '@3dsource/metabox-modular-configurator-api';
*
* //...Assume that the integration is already implemented
* window.env3DSource.apiReady = (api: Communicator) => {
* api.setCommandToMetaBox(new ShowEmbeddedMenu(true));
* };
*/
class ShowEmbeddedMenu extends CommandBase {
/**
* Creates an instance of ShowEmbeddedMenu.
*
* @param {boolean} visible - A flag indicating whether the embedded menu should be visible.
*/
constructor(visible) {
super();
this.data = {
action: MetaboxModularConfiguratorActions.showEmbeddedMenu,
payload: { visible },
};
}
}
/**
* Represents a command to toggle the Unreal Overlay Interface Menu.
*
* @remarks
* This action sends a message to the Metabox API to toggle the visibility of the Unreal overlay UI.
*
* @example
* import { ShowOverlayInterface, Communicator } from '@3dsource/metabox-modular-configurator-api';
*
* //...Assume that the integration is already implemented
* window.env3DSource.apiReady = (api: Communicator) => {
* api.setCommandToMetaBox(new ShowOverlayInterface(true));
* };
*/
class ShowOverlayInterface extends CommandBase {
/**
* Creates an instance of ShowOverlayInterface.
*
* @param {boolean} visible - A flag indicating whether the Unreal overlay UI should be visible.
*/
constructor(visible) {
super();
this.data = {
action: MetaboxModularConfiguratorActions.showOverlayInterface,
payload: { visible },
};
}
}
/**
* EventDispatcher is a class that manages event listeners and dispatches events to them.
*/
class EventDispatcher {
constructor() {
/**
* Storage for callback listeners by message type.
*/
this.listeners = [];
}
destroy() {
this.listeners = [];
}
/**
* Adds an event listener for receiving specific types of messages.
*
* @param {string} messageType - The message type to listen for.
* @param callback - The callback function to execute when a message is received.
*/
addEventListener(messageType, callback) {
this.listeners.push({ messageType, callback });
return this;
}
/**
* Dispatches an event to all listeners of a specific message type.
*
* @param {string} messageType - The message type.
* @param data - The data associated with the event.
*/
dispatchEvent(messageType, data) {
this.listeners
.filter((listener) => listener.messageType === messageType)
.forEach((listener) => listener.callback(data));
return this;
}
/**
* Removes an event listener for a specific type of message.
*
* @param {string} messageType - The message type.
* @param callback - The callback function to remove.
*/
removeEventListener(messageType, callback) {
this.listeners = this.listeners.filter((listener) => !(listener.messageType === messageType && listener.callback === callback));
return this;
}
}
/**
* Handles messaging between the host page and embedded to the Metabox content.
* @internal Use {@link Communicator.createInstance} or the {@link integrateMetabox} helper to instantiate.
*/
class Communicator extends EventDispatcher {
/**
* Singleton reference to the current communicator instance.
*/
static { this.instance = null; }
/**
* Constructs a Communicator, replacing any existing instance, and begins listening for messages.
* @internal
*/
constructor() {
super();
/**
* Bound handler for incoming postMessage events.
*/
this.binder = this.handleMessageReceived.bind(this, 'metaboxData');
Communicator.instance?.destroy();
Communicator.instance = this;
window.addEventListener('message', this.binder);
}
/**
* Listens for to the Metabox to signal readiness, then initializes communicator.
* @param apiReadyCallback - Called with the new Communicator once to the Metabox is loaded.
*/
static createInstance(apiReadyCallback) {
const startHandler = (event) => {
const message = event.data;
if (message?.envelope?.action === 'appLoaded') {
apiReadyCallback(new Communicator());
window.removeEventListener('message', startHandler);
}
};
window.addEventListener('message', startHandler);
}
/**
* Cleans up resources and stops listening for messages.
*/
destroy() {
super.destroy();
window.removeEventListener('message', this.binder);
}
/**
* Posts a command to the Metabox iframe.
* @param command - An action command containing data to send.
*/
sendCommandToMetaBox(command) {
const { data } = command;
const iframe = document.getElementById('embeddedContent');
if (!iframe?.contentWindow) {
console.warn('to the Metabox IFrame not found or not ready.');
return;
}
const message = {
host: 'metaBoxHost',
payload: { ...data },
};
// Replace '*' with a specific origin in production for better security.
iframe.contentWindow.postMessage(message, '*');
}
/**
* Registers an event listener for messages dispatched by Metabox.
* @override
*/
addEventListener(messageType, callback) {
return super.addEventListener(messageType, callback);
}
/**
* Dispatches a typed event to all registered listeners.
* @override
*/
dispatchEvent(messageType, data) {
return super.dispatchEvent(messageType, data);
}
/**
* Removes a previously registered event listener.
* @override
*/
removeEventListener(messageType, callback) {
return super.removeEventListener(messageType, callback);
}
/**
* Filters and dispatches incoming messages from to the Metabox.
* @param messageType - Expected message type for filtering ('metaboxData').
* @param event - The postMessage event received on a window.
*/
handleMessageReceived(messageType, event) {
// Optionally validate event.origin for security.
if (messageType !== 'metaboxData' || event.data?.host !== 'metabox') {
return;
}
const { eventType, payload } = event.data.envelope;
this.dispatchEvent(eventType, payload);
}
}
/**
* Integrates Metabox Modular Configurator into the provided layout.
*
* @remarks
* This function injects an iframe into a specified container element in the DOM and initializes
* the communicator for interacting with the Metabox embedded content.
*
* @param iframeSrc - The source URL of the Metabox iframe.
* Additional url parameters:
* ```javascript
* sidebar=false - disables the internal Metabox menu
* introImage=https://example.com/image.png - sets the intro image
* ```
* i.e., a final link will look like https://metabox.3dsource.com/metabox-configurator/modular/your-configurator-id?sidebar=false&introImage=https://example.com/image.png
*
* @param {string} containerId - The ID of the layout element. Defaults to 'embed3DSource'.
* @param apiReadyCallback
* @throws Error if the container element with the specified ID is not found.
*
* @example
* import {
* integrateMetabox,
* } from '@3dsource/metabox-modular-configurator-api';
*
* integrateMetabox(
* 'https://metabox.3dsource.com/metabox-configurator/modular/12345678-1234-1234-1234-01234567890?introImage=https://example.com/intro.webp&sidebar=false',
* 'embed3DSource'
* );
*/
function integrateMetabox(iframeSrc, containerId = 'embed3DSource', apiReadyCallback) {
const container = document.getElementById(containerId);
if (!container) {
throw new Error(`Container element with id ${containerId} not found`);
}
const existingIframe = document.getElementById('embeddedContent');
if (existingIframe) {
existingIframe.remove();
}
Communicator.createInstance(apiReadyCallback);
const style = document.createElement('style');
style.innerHTML = `
#${containerId} {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
}
`;
document.head.appendChild(style);
const iframe = document.createElement('iframe');
iframe.setAttribute('referrerPolicy', 'no-referrer-when-downgrade');
iframe.setAttribute('id', 'embeddedContent');
iframe.style.border = '0';
iframe.style.width = '100%';
iframe.style.height = '100%';
iframe.style.overflow = 'hidden';
iframe.src = iframeSrc;
container.appendChild(iframe);
}
/**
* Saves an image by triggering a download.
*
* @remarks
* This function creates an anchor element, sets its `href` attribute to the provided image URL,
* and triggers a click event to initiate a download with the specified filename.
*
* @param {string} imageUrl - The URL of the image to save.
* @param {string} filename - The name of the file to save.
*/
function saveImage(imageUrl, filename) {
const a = document.createElement('a');
a.href = imageUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
/**
* Generated bundle index. Do not edit.
*/
export { ApplyZoom, CommandBase, Communicator, EventDispatcher, GetPdf, GetScreenshot, InitShowcase, MetaboxModularConfiguratorActions, PauseShowcase, PlayShowcase, ResetCamera, SetComponent, SetComponentMaterial, SetComponentType, SetEnvironment, SetEnvironmentMaterial, ShowEmbeddedMenu, ShowOverlayInterface, StopShowcase, UnrealCommand, fromCommunicatorEvent, integrateMetabox, saveImage };
//# sourceMappingURL=3dsource-metabox-modular-configurator-api.mjs.map