Example Implementation
This section gives a full example of a minimal host adator which builts on the PICTOFiT Platform. The platform provides a public endpoint which returns stock assets (default avatars, garments and scenes). Keep in mind that this is an example. For your actual implementation, you will have to either host the data yourself or use our PICTOFiT Platform.
You can find the full source in our sample repository.
Fetching the Data
The PICTOFiT Platform provides a a public GraphQL API that lets you fetch the stock assets via the getStockResources
query. A simple query for stock garments could look like this:
query{
getStockResources(forFormat:WEB, forKind:GARMENT_2D) {
id,
name,
thumbnail,
assets {
files {
fileName,
getLocation
}
}
}
}
This will return a list of stock garments where each entry looks like this:
{
"id": "168db2f6-8a7d-4b4e-ae5e-883480396c88",
"name": "Snow Peak 3 Layer Rain Jacket",
"thumbnail": "https://pictofit-platform.f21e22a4fbbf2fdc7fbae60413530984.r2.cloudflarestorage.com/168db2f6-8a7d-4b4e-ae5e-883480396c88/c2d420ce-88ee-4a60-b1ba-9cb86f05a90e?x-id=GetObject&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=fa329d5876ec80cb82dbf8e4b59fa5ff%2F20231016%2Fauto%2Fs3%2Faws4_request&X-Amz-Date=20231016T090508Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=d20453135003831f29c1f825bbfd1144ccb60986a2303a342e9887c4f4ee3ac5",
"assets": [
{
"files": [
{
"fileName": "diffuse.jpg",
"getLocation": "https://pictofit-platform.f21e22a4fbbf2fdc7fbae60413530984.r2.cloudflarestorage.com/168db2f6-8a7d-4b4e-ae5e-883480396c88/3e2fcd55-a80b-4e42-bda4-27854ca02c13?x-id=GetObject&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=fa329d5876ec80cb82dbf8e4b59fa5ff%2F20231016%2Fauto%2Fs3%2Faws4_request&X-Amz-Date=20231016T090508Z&X-Amz-Expires=172800&X-Amz-SignedHeaders=host&X-Amz-Signature=da0fee3d0a0e6d4f3e10f6f5e57aaf21b21daa89f6b122dda7e29dc6291e4c75"
},
...
]
}]
}
This result already has all the information we need. It provides an ID, a display name, a thumbnail and the assets for a garment. So let’s define a simple function to fetch this data from the endpoint:
/**
* Helper function to fetch public resources from the PICTOFiT platform API
*/
function fetchPublicResources(kind : 'GARMENT_2D' | 'SCENE' | 'AVATAR_2D') : Promise<Resource[]> {
const graphQLQuery = `
query {
getStockResources(forFormat:WEB, forKind:${kind}) {
id,
name,
thumbnail,
assets {
files {
fileName,
getLocation
}
}
}
}`;
return fetch("https://api.platform.pictofit.com/graphql/public/v1", {
method : 'POST',
headers: {
accept: "application/json, multipart/mixed",
},
body: JSON.stringify( { query : graphQLQuery })
})
.then((res) => res.json()) // conver the result to json
.then((json) => {
return json.data.getStockResources; // return only the actual relevant data part of the response
});
}
Line 6-19 define the GraphQL query which returns the data in the format from above. We have to provide the kind of resource we want to fetch to the query since we have to write different providers for garments, avatars and scenes. We then call fetch and provide the query as JSON in the body of the request. The resulting data is transformed into a JSON object. Finally, we extract the getStockResource
field as the GraphQL wraps the result into an object containing metadata for the request.
We have defined the Resource
interface to get code completion when accessing the result of the GraphQL query.
interface Resource {
id : string,
name : string,
thumbnail : string,
assets : {
files : {
fileName : string,
getLocation : string
}[]
}[]
}
Implementing a Garment Provider
Now let’s define a Providers.GarmentProvider
to fill our dressing room with first content. The interface look like this:
export interface GarmentProvider extends AssetProvider {
getIds(): Awaitable<Array<string>>;
getById(id: string): Awaitable<Core.AssetInfo | undefined>;
getName(id: string): Awaitable<string | undefined>;
getThumbnail(id: string): Awaitable<string | undefined>;
removeById(id: string): Awaitable<void>;
/** Callback to add a garments from a shared look into the current dressing room / garment provider. */
addById?: (id: string) => void;
}
Please be aware that getIds()
and getById()
are actually defined by AssetProvider
which is the underlying interface for all resources which need to provide assets that can be consumed by the Web SDK (garment, avatars, scenes). First, let’s fetch all garments and provide their IDs.
class SimpleGarmentProvider implements Providers.GarmentProvider {
private _items : Promise<Resource[]>;
constructor() {
this._items = fetchPublicResources("GARMENT_2D");
}
public getIds(): Awaitable<string[]> {
return this._items.then((assets) => {
return assets.map((asset) => asset.id)
});
}
// ...
}
We first fetch the resources of kind GARMENT_2D
from the PICTOFiT Platform and then simply extract their IDs using Javascript’s Array.map(...)
and return them. Now let’s continue with the name and the thumbnail which are again straightforward.
public getName(id: string): Awaitable<string | undefined> {
return this._items.then((assets) => {
const asset = assets.find((asset) => asset.id === id);
if(asset === undefined) {
return undefined;
}
return asset.name;
})
}
public getThumbnail(id: string): Awaitable<string | undefined> {
return this._items.then((assets) => {
const asset = assets.find((asset) => asset.id === id);
if(asset === undefined) {
return undefined;
}
return asset.thumbnail;
})
}
We first check if a resource with the given ID exists. If that’s the case we return the name/thumbnail. Now the remaining part is the getById()
method which returns the assets for a resource.
public getById(id: string): Awaitable<Core.AssetInfo | undefined> {
return this._items.then((assets) => {
const asset = assets.find((asset) => asset.id === id);
if(asset === undefined) {
return undefined;
}
return asset.assets[0].files.reduce((init, file) => ({
...init,
[file.fileName] : file.getLocation
}), {} );
})
}
Again, we check if a resource with the given ID exist. If that’s the case, we use the Array.reduce(…) method to transform the files array which contains objects for each file with name and location as separate properties into the form filename : location
. Please see the Introduction section for more details on the format of the Core.AssetInfo
object.
We have left out removeById()
and addById()
for the sake of simplicity in this example. Apart from that, this is all you need to implement to populate the dressing room with garments. Repeat this for the avatars and scenes and you are good to go. You can check out our sample repository which has a full, working implementation. Note that we extract the common operations into a generic provider called GenericPublicAssetProvider
to avoid code duplication. Now let’s assemble our actual host provider:
export const simpleHostAdapter : HostAdaptor = {
garments : {
"2D_Parallax" : new SimpleGarmentProvider()
},
avatars : {
"2D_Parallax" : new SimpleAvatarProvider()
},
scenes : {
"2D_Parallax" : new SimpleSceneProvider()
},
dressingRoom : {
mode : "2D_Parallax"
}
}
The MDR supports different modes so we have to define for which mode the respective resource provider is designed. Finally, we set the mode of the dressing room to 2D_Parallax
. What now remains is to create an instance of the dressing room and to pass the host provider to it.
Integrating the MDR
Finally, we need to create the pictofit-modal-dressing-room
element, add it to the DOM and assign our HostAdapter
.
import { simpleHostAdapter } from './host-adapter';
import { HostAdaptor } from '@reactivereality/pictofit-dressing-room';
import "@reactivereality/pictofit-dressing-room/bundle"
window.getHostAdapter = function() {
return simpleHostAdapter;
}
const url = "https://spf-app.pictofit.dev";
const dressingRoom = document.createElement("pictofit-modal-dressing-room")
dressingRoom.setAttribute('host-adaptor', 'getHostAdapter')
dressingRoom.setAttribute('compute-server', url)
document.body.appendChild(dressingRoom);
We define a factory function getHostAdapter which returns our simpleHostAdapter and pass it to the MDR web component. The MDR is expecting this method to be part of the window object. Now to actually show the dressing room, we add e.g. a link to the html and register a click event handler that opens the MDR:
document.querySelector<HTMLDivElement>('#open-dressing-room')!.addEventListener('click', () => {
const dressingRoom = document.querySelector("pictofit-modal-dressing-room");
dressingRoom!.open();
})
That’s it, you have successfully created a host adapter to populate the MDR with content and related functionality. Please remember that the source of the data can be anything. Ideally this data comes from your e-commerce solution and your CDN. Alternatively, we can host your assets for you on our PICTOFiT Platform. Read more about this in the next section. Be aware that this is sample code and not production ready.