Skip to content

Commit

Permalink
added section on structure
Browse files Browse the repository at this point in the history
  • Loading branch information
codetricity committed May 31, 2024
1 parent 68f6ec5 commit 17872e2
Show file tree
Hide file tree
Showing 2 changed files with 374 additions and 0 deletions.
373 changes: 373 additions & 0 deletions docs/structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,373 @@
# Project Structure

To use the viewer, build a simple server to provide routes
for the UI that you embed the viewer in.


This example uses [Express](https://expressjs.com/) for the server.
Let's say you create a route, `/content`. The front-end application
goes to `http://localhost:3000/content` and then Express will run
a method that you created. In this example, the method is called
`getContent()`.

Prior to actually getting the content, you must first generate a token.
The method `getContent()` will first generate a token, then it will
get the content from the RICOH360 Content API. The token is put into
the Authorization header.



The example below uses the JavaScript [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) package.

```
fetch("https://api.ricoh360.com/contents", {
method: "GET",
headers: {
Authorization: "Bearer " + tokenObject.access_token,
},
});
```



- **views/**: Folder that holds view/HTML files (EJS markdown files)
- **index.ejs**: Home/landing page template
- **viewer.ejs**: Viewer page template
- **index.js**: File where server side functions and API endpoints to these functions are held

## Tokens

The demo uses two different tokens. There is a token for the viewer and a token for the content API. It can be confusing. Don't worry, we'll
walk you through it.

The token for the Content API is generated with Amazon Cognito with
the Client ID and Client Secret.

The token for the RICOH Viewer is generated with the Client ID
and Private Key
using [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken).

The Private Key and the Client Secret are not the same.

We provide examples for both tokens and show how to use them
in your API calls.

## Server-side functions

### createToken()

```typescript
const privatekey = `-----BEGIN PRIVATE KEY-----
<client private key>
-----END PRIVATE KEY-----`;

const createToken = () => {
const payload = {
client_id: <your client id>,
};

const accessToken = jwt.sign(payload, privatekey, {
algorithm: "RS256",
expiresIn: "60m",
});

return accessToken;
};
```

- `createToken()` uses a payload object with your clientID and optionally your groupID
- Using [jsonwebtoken package](https://www.npmjs.com/package/jsonwebtoken), you create and return an access token with an encryption algorithm that expires in a certain time (60 minutes)
- This function will be called client side via the API endpoint for this function and used in a callback function for Ricoh's viewer API

### getContent()

```typescript
const clientId = "<your client id>";
const clientSecret = "<your client secret>";

const getContent = async () => {
const tokenEndpoint =
"https://saas-prod.auth.us-west-2.amazoncognito.com/oauth2/token";

const auth = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");

const requestData = {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${auth}`,
},
body: new URLSearchParams({
grant_type: "client_credentials",
scope: "all/read",
}),
};

const tokenResponse = await fetch(tokenEndpoint, requestData);
const tokenObject = await tokenResponse.json();

const res = await fetch("https://api.ricoh360.com/contents", {
method: "GET",
headers: {
Authorization: "Bearer " + tokenObject.access_token,
},
});
const data = await res.json();

return data;
};
```

- This function takes your clientID and clientSecret, turns it into base64 string format and sends a post request to https://saas-prod.auth.us-west-2.amazoncognito.com/oauth2/token (set as tokenEndpoint) to get a token to use for Ricoh content API
- After getting the token response, it creates another post request to Ricoh content API with the token as authorization and returning the data

## Server-side endpoints

### /

```
app.get("/", (req, res) => {
res.render("index");
});
```

This endpoint returns the EJS template file (index.ejs) in ./views


### /viewer

```typescript
app.get("/viewer", (req, res) => {
res.render("viewer");
});
```
Calls up the main viewer with the split view, transformations, and
content.

### /token

```typescript
app.get("/token", (req, res) => {
let token = createToken();
res.status(200).send(token);
});
```

This endpoint calls on the createToken() server-side function and returns the token

### /content

```javascript
app.get("/content", async (req, res) => {
let test = await getContent();
res.status(200).send(test);
});
```

This endpoint calls on the getContent() async function and returns the content data which is needed by the viewer.

## Client-side (in ./views/index.ejs)

### fetchContent

```typescript
const fetchContent = async () => {

// fetch content from /content endpoint and store data in const data
const res = await fetch("/content", {
method: "GET",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
const data = await res.json();

// populate buttons on left pane list
const leftList = document.getElementById("leftList");
for (let i = 0; i < data.length; i++) {
if (data[i].status === "uploaded") {
console.log(data[i]);
const listItem = document.createElement("li");
listItem.innerHTML = `<div>
<img src="${
data[i].thumbnail_url
}" onclick="viewer.switchScene({ contentId: '${
data[i].content_id
}'},${0})">
<button type="button" onclick="viewer.switchScene({ contentId: '${
data[i].content_id
}',transform:'enhancement'},${0})">
Enhance
</button>
<button type="button" onclick="viewer.switchScene({ contentId: '${
data[i].content_id
}',transform:'b_person'},${0})">
Blur People
</button>
<button type="button" onclick="viewer.switchScene({ contentId: '${
data[i].content_id
}',transform:'p_cubic'},${0})">
Cubic View
</button>
</div>`;
leftList.append(listItem);
}
}

// populate buttons on right pane list
const rightList = document.getElementById("rightList");
for (let i = 0; i < data.length; i++) {
if (data[i].status === "uploaded") {
console.log(data[i]);
const listItem = document.createElement("li");
listItem.innerHTML = `<div>
<img src="${
data[i].thumbnail_url
}" onclick="viewer.switchScene({ contentId: '${
data[i].content_id
}'},${1})">
<button type="button" onclick="viewer.switchScene({ contentId: '${
data[i].content_id
}',transform:'enhancement'},${1})">
Enhance
</button>
<button type="button" onclick="viewer.switchScene({ contentId: '${
data[i].content_id
}',transform:'b_person'},${1})">
Blur People
</button>
<button type="button" onclick="viewer.switchScene({ contentId: '${
data[i].content_id
}',transform:'p_cubic'},${1})">
Cubic View
</button>
</div>`;
rightList.append(listItem);
}
}
};
```

`fetchContent()` makes a async get request to the backend endpoint /content and returns content data and populates listing of images with transform buttons

### fetchToken()

```javascript
const fetchToken = async () => {
const res = await fetch("/token", {
method: "GET",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
const data = await res.text();
return data;
};
```

`fetchToken()` makes a async get request to the backend endpoint /token and returns a token

### ui

```typescript
const ui = {
cropping: {
horizontalMargin: 15,
verticalMargin: 75,
},
toolbar: {
isHidden: false,
cancelButton: {
label: "Cancel",
},
checkButton: {
label: "Done",
},
autoRotateButton: {
isHidden: false,
label: "Auto Rotate",
},
croppingButton: {
isHidden: false,
enterLabel: "Enter Cropping",
exitLabel: "Exit Cropping",
},
drawingButton: {
isHidden: false,
enterLabel: "Enter Drawing",
exitLabel: "Exit Drawing",
},
fullScreenButton: {
isHidden: false,
enterLabel: "Enter Full Screen",
exitLabel: "Exit Full Screen",
},
homePositionButton: {
isHidden: false,
label: "Home Position",
},
liveStreamingControlButtons: {
isHidden: false,
startLabel: "Start Streaming",
stopLabel: "Stop Streaming",
speakerOnLabel: "Speaker On",
speakerOffLabel: "Speaker Off",
},
zoomControlButtons: {
isHidden: false,
zoomInLabel: "Zoom In",
zoomOutLabel: "Zoom Out",
},
},
tooltip: {
isHidden: false,
},
verticalPanel: {
isHidden: false,
topMargin: 25,
},
};
```

- Const ui is a parameter that is passed into the viewer object
- Allows definition and hiding/showing of ui buttons and functionalities

### onCropped

```typescript
const onCropped = async (url) => {
const image = await fetch(url);
const imageBlob = await image.blob();
const imageURL = URL.createObjectURL(imageBlob);
const downloadLink = document.createElement('a');
downloadLink.href = imageURL;
downloadLink.download = 'cropped_image.jpeg';
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
};
```

In the viewer, there is a crop function, that crop function passes in the cropped image URL to this callback function and downloads the cropped image to your computer.

### viewer

```typescript
const viewer = new RICOH360Viewer({
divId: "viewer",
onFetchToken: fetchToken,
isCubemapEnabled: true,
ui,
onSelected: (index) => {
console.log("selected", index);
},
});

viewer.start({
contentId: "6b004f76-60cf-4f14-ae3c-49b7dee4aca2",
transform: "enhancement",
});
```
- Viewer is Ricoh embedded viewer API
- Viewer is initialized by specific a divId to inject the viewer UI into, an onFetchToken function for authorization and a CubeMapEnabled
- Viewer is started with a contentID (call fetchContent() function to get contentIDs) and a transform value ie. "enhancement" or "undefined"
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ nav:
- Demo Overview: overview.md
- Cloud: cloud.md
- Features: features.md
- Structure: structure.md
- Contact: contact.md

0 comments on commit 17872e2

Please sign in to comment.