Skip to content

Commit 5f9061a

Browse files
committed
Initial Commit
1 parent 66edb09 commit 5f9061a

15 files changed

+23612
-1
lines changed

README.md

+42-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,42 @@
1-
# banana-app
1+
# The Banana App
2+
3+
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app) using the iTwin template `npx create-react-app your-app-name --template @bentley/itwin-viewer --scripts-version @bentley/react-scripts`
4+
5+
## Environment Variables
6+
7+
Prior to running the app, add a valid contextId and iModelId for your user in the .env file:
8+
9+
```
10+
# ---- Test ids ----
11+
IMJS_CONTEXT_ID = ""
12+
IMJS_IMODEL_ID = ""
13+
```
14+
15+
You can also replace the OIDC client data in this file with your own if you'd prefer.
16+
17+
## Available Scripts
18+
19+
In the project directory, you can run:
20+
21+
### `npm start`
22+
23+
Runs the app in the development mode.\
24+
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
25+
26+
The page will reload if you make edits.\
27+
You will also see any lint errors in the console.
28+
29+
### `npm test`
30+
31+
Launches the test runner in the interactive watch mode.\
32+
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
33+
34+
### `npm run build`
35+
36+
Builds the app for production to the `build` folder.\
37+
It correctly bundles React in production mode and optimizes the build for the best performance.
38+
39+
The build is minified and the filenames include the hashes.\
40+
Your app is ready to be deployed!
41+
42+
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.

package-lock.json

+23,022
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{
2+
"name": "banana-app",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"@bentley/bentleyjs-core": "^2.13.3",
7+
"@bentley/extension-client": "^2.13.3",
8+
"@bentley/frontend-application-insights-client": "^2.13.3",
9+
"@bentley/frontend-authorization-client": "^2.13.3",
10+
"@bentley/geometry-core": "^2.13.3",
11+
"@bentley/icons-generic": "^1.0.34",
12+
"@bentley/imodelhub-client": "^2.13.3",
13+
"@bentley/imodeljs-common": "^2.13.3",
14+
"@bentley/imodeljs-frontend": "^2.13.3",
15+
"@bentley/imodeljs-i18n": "^2.13.3",
16+
"@bentley/imodeljs-markup": "^2.13.3",
17+
"@bentley/imodeljs-quantity": "^2.13.3",
18+
"@bentley/itwin-client": "^2.13.3",
19+
"@bentley/itwin-viewer-react": "^6.1.4",
20+
"@bentley/orbitgt-core": "^2.13.3",
21+
"@bentley/presentation-common": "^2.13.3",
22+
"@bentley/presentation-frontend": "^2.13.3",
23+
"@bentley/product-settings-client": "^2.13.3",
24+
"@bentley/rbac-client": "^2.13.3",
25+
"@bentley/react-scripts": "^3.4.10",
26+
"@bentley/telemetry-client": "^2.13.3",
27+
"@bentley/ui-abstract": "^2.13.3",
28+
"@bentley/ui-components": "^2.13.3",
29+
"@bentley/ui-core": "^2.13.3",
30+
"@bentley/ui-framework": "^2.13.3",
31+
"@bentley/ui-ninezone": "^2.13.3",
32+
"@bentley/webgl-compatibility": "^2.13.3",
33+
"@svgr/webpack": "^4.3.3",
34+
"@testing-library/jest-dom": "^4.2.4",
35+
"@testing-library/react": "^10.4.9",
36+
"@testing-library/user-event": "^7.2.1",
37+
"@types/history": "^4.7.8",
38+
"@types/jest": "^26.0.22",
39+
"@types/node": "^14.14.37",
40+
"@types/react": "^16.14.5",
41+
"@types/react-dom": "^16.9.12",
42+
"history": "^4.10.1",
43+
"oidc-client": "^1.11.5",
44+
"react": "^16.13.1",
45+
"react-dom": "^16.13.1",
46+
"typescript": "^3.9.9"
47+
},
48+
"scripts": {
49+
"start": "react-scripts --max_old_space_size=4096 start",
50+
"build": "react-scripts --max_old_space_size=4096 build",
51+
"test": "react-scripts --max_old_space_size=4096 test",
52+
"eject": "react-scripts eject"
53+
},
54+
"eslintConfig": {
55+
"extends": [
56+
"react-app",
57+
"react-app/jest"
58+
]
59+
},
60+
"browserslist": [
61+
"electron 8.0.0",
62+
"last 4 chrome version",
63+
"last 4 firefox version",
64+
"last 4 safari version",
65+
"last 4 ios version",
66+
"last 4 ChromeAndroid version",
67+
"last 4 edge version",
68+
"not dead",
69+
"not <0.2%"
70+
]
71+
}

public/index.html

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<meta name="theme-color" content="#000000" />
7+
<meta
8+
name="description"
9+
content="iTwin Viewer based website created using create-react-app"
10+
/>
11+
<!--
12+
Notice the use of %PUBLIC_URL% in the tags above.
13+
It will be replaced with the URL of the `public` folder during the build.
14+
Only files inside the `public` folder can be referenced from the HTML.
15+
16+
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
17+
work correctly both with client-side routing and a non-root public URL.
18+
Learn how to configure a non-root public URL by running `npm run build`.
19+
-->
20+
<title>iTwin Viewer React Sample</title>
21+
</head>
22+
<body>
23+
<noscript>You need to enable JavaScript to run this app.</noscript>
24+
<div id="root"></div>
25+
<!--
26+
This HTML file is a template.
27+
If you open it directly in the browser, you will see an empty page.
28+
29+
You can add webfonts, meta tags, or analytics to this file.
30+
The build step will place the bundled scripts into the <body> tag.
31+
32+
To begin the development, run `npm start` or `yarn start`.
33+
To create a production bundle, use `npm run build` or `yarn build`.
34+
-->
35+
</body>
36+
</html>

src/App.scss

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.viewer-container {
2+
height: calc(100vh - 50px);
3+
}

src/App.tsx

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import "./App.scss";
2+
3+
import { Viewer } from "@bentley/itwin-viewer-react";
4+
import React, { useEffect, useState } from "react";
5+
6+
import AuthorizationClient from "./AuthorizationClient";
7+
import { Header } from "./Header";
8+
import { Banana } from "./providers/Banana";
9+
10+
const App: React.FC = () => {
11+
const [isAuthorized, setIsAuthorized] = useState(
12+
AuthorizationClient.oidcClient
13+
? AuthorizationClient.oidcClient.isAuthorized
14+
: false
15+
);
16+
const [isLoggingIn, setIsLoggingIn] = useState(false);
17+
const [contextId, setContextId] = useState("");
18+
const [iModelId, setiModelId] = useState("");
19+
const [isConnected, setIsConnected] = useState(false);
20+
21+
useEffect(() => {
22+
const initOidc = async () => {
23+
if (!AuthorizationClient.oidcClient) {
24+
await AuthorizationClient.initializeOidc();
25+
}
26+
27+
try {
28+
// attempt silent signin
29+
await AuthorizationClient.signInSilent();
30+
setIsAuthorized(AuthorizationClient.oidcClient.isAuthorized);
31+
} catch (error) {
32+
// swallow the error. User can click the button to sign in
33+
}
34+
};
35+
initOidc().catch((error) => console.error(error));
36+
}, []);
37+
38+
useEffect(() => {
39+
if (isLoggingIn && isAuthorized) {
40+
setIsLoggingIn(false);
41+
}
42+
}, [isAuthorized, isLoggingIn]);
43+
44+
const onLoginClick = async () => {
45+
setIsLoggingIn(true);
46+
await AuthorizationClient.signIn();
47+
};
48+
49+
const onLogoutClick = async () => {
50+
setIsLoggingIn(false);
51+
await AuthorizationClient.signOut();
52+
setIsAuthorized(false);
53+
};
54+
55+
const onContextIdChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
56+
setContextId(e.target.value);
57+
}
58+
59+
const oniModelChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
60+
setiModelId(e.target.value);
61+
}
62+
63+
const onConnectClick = async () => {
64+
setIsConnected(true);
65+
}
66+
67+
return (
68+
<div className="viewer-container">
69+
<Header
70+
handleLogin={onLoginClick}
71+
loggedIn={isAuthorized}
72+
handleLogout={onLogoutClick}
73+
contextId={contextId}
74+
handleContextIdChange={onContextIdChange}
75+
iModelId={iModelId}
76+
handleiModelIdChange={oniModelChange}
77+
handleConnectToiModel={onConnectClick}
78+
/>
79+
{isLoggingIn ? (
80+
<span>"Logging in...."</span>
81+
) : !isConnected ? (
82+
<span>"Entery context and iModel Ids then click Connect</span>
83+
) : (
84+
isAuthorized && (
85+
<Viewer
86+
contextId={contextId}
87+
iModelId={iModelId}
88+
authConfig={{ oidcClient: AuthorizationClient.oidcClient }}
89+
uiProviders={[new Banana()]}
90+
/>
91+
)
92+
)}
93+
</div>
94+
);
95+
};
96+
97+
export default App;

src/AuthorizationClient.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {
2+
BrowserAuthorizationCallbackHandler,
3+
BrowserAuthorizationClient,
4+
BrowserAuthorizationClientConfiguration,
5+
} from "@bentley/frontend-authorization-client";
6+
import { FrontendRequestContext } from "@bentley/imodeljs-frontend";
7+
8+
class AuthorizationClient {
9+
private static _oidcClient: BrowserAuthorizationClient;
10+
11+
public static get oidcClient(): BrowserAuthorizationClient {
12+
return this._oidcClient;
13+
}
14+
15+
public static async initializeOidc(): Promise<void> {
16+
if (this._oidcClient) {
17+
return;
18+
}
19+
20+
const scope = process.env.IMJS_AUTH_CLIENT_SCOPES ?? "";
21+
const clientId = process.env.IMJS_AUTH_CLIENT_CLIENT_ID ?? "";
22+
const redirectUri = process.env.IMJS_AUTH_CLIENT_REDIRECT_URI ?? "";
23+
const postSignoutRedirectUri = process.env.IMJS_AUTH_CLIENT_LOGOUT_URI;
24+
25+
// authority is optional and will default to Production IMS
26+
const oidcConfiguration: BrowserAuthorizationClientConfiguration = {
27+
clientId,
28+
redirectUri,
29+
postSignoutRedirectUri,
30+
scope,
31+
responseType: "code",
32+
};
33+
34+
await BrowserAuthorizationCallbackHandler.handleSigninCallback(
35+
oidcConfiguration.redirectUri
36+
);
37+
38+
this._oidcClient = new BrowserAuthorizationClient(oidcConfiguration);
39+
}
40+
41+
public static async signIn(): Promise<void> {
42+
await this.oidcClient.signIn(new FrontendRequestContext());
43+
}
44+
45+
public static async signInSilent(): Promise<void> {
46+
await this.oidcClient.signInSilent(new FrontendRequestContext());
47+
}
48+
49+
public static async signOut(): Promise<void> {
50+
await this.oidcClient.signOut(new FrontendRequestContext());
51+
}
52+
}
53+
54+
export default AuthorizationClient;

src/Header.module.scss

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.header {
2+
background-color: #282c34;
3+
4+
.buttonContainer {
5+
text-align: center;
6+
7+
button {
8+
width: 100px;
9+
margin: 10px !important;
10+
}
11+
}
12+
}

src/Header.tsx

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Button, ButtonType } from "@bentley/ui-core";
2+
import React from "react";
3+
4+
import styles from "./Header.module.scss";
5+
6+
interface HeaderProps {
7+
handleLogin: () => void;
8+
handleLogout: () => void;
9+
loggedIn: boolean;
10+
contextId: string;
11+
handleContextIdChange: React.ChangeEventHandler<HTMLInputElement>;
12+
iModelId: string;
13+
handleiModelIdChange: React.ChangeEventHandler<HTMLInputElement>;
14+
handleConnectToiModel: () => void;
15+
}
16+
17+
export const Header: React.FC<HeaderProps> = ({
18+
loggedIn,
19+
handleLogin,
20+
handleLogout,
21+
contextId,
22+
handleContextIdChange,
23+
iModelId,
24+
handleiModelIdChange,
25+
handleConnectToiModel,
26+
}: HeaderProps) => {
27+
return (
28+
<header className={styles.header}>
29+
<div className={styles.buttonContainer}>
30+
<Button
31+
className={styles.button}
32+
onClick={handleLogin}
33+
buttonType={ButtonType.Primary}
34+
disabled={loggedIn}
35+
>
36+
{"Sign In"}
37+
</Button>
38+
<Button
39+
className={styles.button}
40+
onClick={handleLogout}
41+
buttonType={ButtonType.Primary}
42+
disabled={!loggedIn}
43+
>
44+
{"Sign Out"}
45+
</Button>
46+
<br/>
47+
Context Id: <input type="text" value={contextId} onChange={handleContextIdChange} />
48+
iModel Id: <input type="text" value={iModelId} onChange={handleiModelIdChange} />
49+
<Button className={styles.button} onClick={handleConnectToiModel} buttonType={ButtonType.Primary}>{"Connect"}</Button>
50+
</div>
51+
</header>
52+
);
53+
};

src/index.scss

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
body {
2+
margin: 0;
3+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4+
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5+
sans-serif;
6+
-webkit-font-smoothing: antialiased;
7+
-moz-osx-font-smoothing: grayscale;
8+
}
9+
10+
code {
11+
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12+
monospace;
13+
}

0 commit comments

Comments
 (0)