The @dcl/inspector
package implements a scene editor interface for Decentraland,
built on React, BabylonJS, and TypeScript. It provides a modular architecture that can be
integrated into different environments through well-defined RPC interfaces and abstractions.
A React component that renders and manages the scene's entity tree. It:
ROOT
, PLAYER
, CAMERA
Tree
component for rendering hierarchical dataHandles component editing through specialized inspectors:
Component-specific inspectors include:
TransformInspector
: Position, rotation, and scaleGltfInspector
: 3D model configurationMaterialInspector
: Material properties and texturesActionInspector
: Smart item action configurationTriggerInspector
: Smart item trigger setupStatesInspector
: Smart item state managementTextShapeInspector
: Text rendering propertiesAudioSourceInspector
: Sound configurationVideoPlayerInspector
: Video playback settingsNftShapeInspector
: NFT display configurationAnimatorInspector
: Animation controlsPointerEventsInspector
: Interaction handlingCounterInspector
: Value trackingVisibilityComponentInspector
: Display controlsMeshColliderInspector
: Collision propertiesMeshRendererInspector
: Rendering settings
A complete list of all available inspectors can be found in the
EntityInspector
folder of the SDK repository.
Features:
Config
component3D scene visualization and manipulation:
The Assets panel provides access to three main sources of content:
Local Assets
Custom Items
Asset Packs
@dcl/asset-packs
packageAdditional functionality includes:
The Data Layer provides a complete interface for all editor operations, handling both scene state and asset management. It serves as the primary communication channel between the Inspector and its host environment, responsible for:
Scene State Management:
Asset Management:
The Data Layer has two implementations:
Local:
Remote:
The storage behavior is determined by the File System Interface implementation used, not by the Data Layer type. For example:
A Local Data Layer could use:
A Remote Data Layer typically uses:
The remote Data Layer is implemented as a protobuf-defined RPC service to ensure type safety and versioning:
service DataService {
// Scene state synchronization
rpc CrdtStream(stream CrdtStreamMessage) returns (stream CrdtStreamMessage)
// Asset management
rpc CreateCustomAsset(CreateCustomAssetRequest) returns (CreateCustomAssetResponse)
rpc GetCustomAssets(Empty) returns (GetCustomAssetsResponse)
rpc GetAssetCatalog(Empty) returns (AssetCatalogResponse)
rpc DeleteCustomAsset(DeleteCustomAssetRequest) returns (Empty)
rpc RenameCustomAsset(RenameCustomAssetRequest) returns (Empty)
// File operations
rpc GetFiles(GetFilesRequest) returns (GetFilesResponse)
rpc SaveFile(SaveFileRequest) returns (Empty)
rpc GetFile(GetFileRequest) returns (GetFileResponse)
rpc CopyFile(CopyFileRequest) returns (Empty)
// Editor state
rpc Undo(Empty) returns (UndoRedoResponse)
rpc Redo(Empty) returns (UndoRedoResponse)
rpc Save(Empty) returns (Empty)
}
The File System Interface is a lower-level abstraction focused solely on file operations. It has three distinct implementations:
In-Memory (feeded-local-fs.ts
):
Node.js (sdk-commands/start/data-layer/fs.ts
):
fs
module
IFrame (iframe-storage.ts
):
fs
moduleThe interface definition remains intentionally simple:
export type FileSystemInterface = {
dirname: (path: string) => string
basename: (filePath: string) => string
join: (...paths: string[]) => string
existFile: (filePath: string) => Promise<boolean>
readFile: (filePath: string) => Promise<Buffer>
writeFile: (filePath: string, content: Buffer) => Promise<void>
readdir: (dirPath: string) => Promise<{ name: string; isDirectory: boolean }[]>
rm: (filePath: string) => Promise<void>
cwd: () => string
}
All additional RPCs are implemented using the
@dcl/mini-rpc
library,
which provides type-safe client/server communication. These include:
Each RPC uses postMessage for transport in IFrame implementations and follows the mini-rpc pattern of:
The Data Layer uses the File System Interface but adds:
Example:
While the File System Interface would only handle the raw file operations without understanding the asset structure or scene context.
The parent application embeds the Inspector in an IFrame and communicates through postMessage:
The parent application needs to set up the RPC bridge to handle file system operations:
function initRpc(iframe: HTMLIFrameElement) {
const transport = new MessageTransport(window, iframe.contentWindow!)
const storage = new StorageRPC(transport)
// Handle file operations
storage.handle("read_file", async ({ path }) => {
return fs.readFile(path)
})
storage.handle("write_file", async ({ path, content }) => {
await fs.writeFile(path, content)
})
storage.handle("exists", async ({ path }) => {
return fs.exists(path)
})
storage.handle("delete", async ({ path }) => {
await fs.rm(path)
})
storage.handle("list", async ({ path }) => {
const files = await fs.readdir(path)
return Promise.all(
files.map(async (name) => ({
name,
isDirectory: await fs.isDirectory(path.join(path, name))
}))
)
})
return {
storage,
dispose: () => storage.dispose()
}
}
Example of embedding the Inspector in a React application:
const CONTENT_URL = "http://localhost:3000" // URL to your iframe content
function InspectorComponent() {
const iframeRef = useRef()
const handleIframeRef = useCallback((iframe) => {
if (iframe) {
iframeRef.current = initRpc(iframe)
}
}, [])
useEffect(() => {
return () => iframeRef.current?.dispose()
}, [])
const params = new URLSearchParams({
dataLayerRpcParentUrl: window.location.origin // this is the url of the parent application
})
const url = `${CONTENT_URL}?${params}` // url where the inspector is being served
return <iframe onLoad={handleIframeRef} src={url} />
}
The CLI integration provides a development-focused approach using WebSocket communication:
Using the Decentraland CLI (@dcl/sdk-commands
):
# Start the CLI server with data layer enabled
npx sdk-commands start --data-layer --port 8001
This creates:
fs
cd packages/@dcl/inspector
npm start
Or you can also install the @dcl/inspector
package in your project, and then
serve it from node_modules, like:
npm install @dcl/inspector
npx http-server node_modules/@dcl/inspector/public
Now access the inspector from the browser, passing the
dataLayerRpcWsUrl
parameter to connect to the CLI's WebSocket server:
http://localhost:3000/?dataLayerRpcWsUrl=ws://127.0.0.1:8001/data-layer
Where localhost:3000
is the Inspector and 127.0.0.1:8001
is the
CLI's WebSocket server.
The Inspector will automatically:
The Inspector can be configured through URL parameters or by injecting a global
InspectorConfig
object:
type InspectorConfig = {
// Data Layer Configuration
dataLayerRpcWsUrl: string | null // WebSocket URL for CLI integration
dataLayerRpcParentUrl: string | null // Parent window URL for IFrame integration
// Smart Items Configuration
binIndexJsUrl: string | null // URL to smart items runtime (used for development of the @dcl/asset-packs module)
disableSmartItems: boolean // Disable smart items functionality
// Content Configuration
contentUrl: string // URL for asset packs content
// Analytics Configuration
segmentKey: string | null // Segment.io write key
segmentAppId: string | null // Application identifier
segmentUserId: string | null // User identifier
projectId: string | null // Current project identifier
}
Example URL with parameters:
https://localhost:8000/?dataLayerRpcWsUrl=ws://127.0.0.1:8001/data-layer&disableSmartItems=true
Example global configuration:
globalThis.InspectorConfig = {
dataLayerRpcWsUrl: "ws://127.0.0.1:8001/data-layer",
contentUrl: "https://builder-items.decentraland.org"
}
The configuration is resolved in the following order:
This section provides concrete examples of how different applications have integrated the Inspector, demonstrating the flexibility of its architecture. Each example showcases a different integration approach, from CLI-based development environments to web and desktop applications, illustrating how the Inspector's modular design accommodates various use cases.
The VSCode extension is a separate product that:
start
command with the --data-layer
flagThe Web Editor integration:
The Creators Hub integration:
fs
module