Browse Source

Adding plugins routes and navigations menu

bodicsek 3 years ago
parent
commit
80a240f74a

+ 6 - 1
.vscode/settings.json

@@ -1,3 +1,8 @@
 {
-    "typescript.tsdk": "node_modules/typescript/lib"
+    "typescript.tsdk": "node_modules/typescript/lib",
+    "files.watcherExclude": {
+        "**/.git/objects/**": true,
+        "**/.git/subtree-cache/**": true,
+        "**/node_modules/*/**": true
+      }
 }

+ 8 - 2
ojet.config.js

@@ -4,12 +4,18 @@
   as shown at https://oss.oracle.com/licenses/upl/
 
 */
-const package = require("./package.json");
+const webpack = require("webpack");
 const merge = require("webpack-merge").merge;
+const package = require("./package.json");
 
 const developmentConfig = {
+  plugins: [
+    new webpack.DefinePlugin({
+      API_URL: JSON.stringify(require("./package.json").config.devApiUrl)
+    })
+  ],
   devServer: {
-    https: false,
+    https: process.env.HTTPS === "true",
     port: 8000,
     allowedHosts: [
       "localhost",

+ 96 - 3
package-lock.json

@@ -1,14 +1,17 @@
 {
   "name": "occ-fw-ui",
-  "version": "1.0.3",
+  "version": "1.0.8",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "occ-fw-ui",
-      "version": "1.0.3",
+      "version": "1.0.8",
       "dependencies": {
-        "@oracle/oraclejet": "^13.0.0"
+        "@oracle/oraclejet": "13.0.3",
+        "react": "npm:@preact/compat@^17.1.2",
+        "react-dom": "npm:@preact/compat@^17.1.2",
+        "react-router-dom": "^6.4.2"
       },
       "devDependencies": {
         "@oracle/ojet-cli": "^13.0.0",
@@ -261,6 +264,14 @@
         "webpack": "^4.0.0 || ^5.0.0"
       }
     },
+    "node_modules/@remix-run/router": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.2.tgz",
+      "integrity": "sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ==",
+      "engines": {
+        "node": ">=14"
+      }
+    },
     "node_modules/@trysound/sax": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@@ -4526,6 +4537,54 @@
         "url": "https://opencollective.com/webpack"
       }
     },
+    "node_modules/react": {
+      "name": "@preact/compat",
+      "version": "17.1.2",
+      "resolved": "https://registry.npmjs.org/@preact/compat/-/compat-17.1.2.tgz",
+      "integrity": "sha512-7pOZN9lMDDRQ+6aWvjwTp483KR8/zOpfS83wmOo3zfuLKdngS8/5RLbsFWzFZMGdYlotAhX980hJ75bjOHTwWg==",
+      "peerDependencies": {
+        "preact": "*"
+      }
+    },
+    "node_modules/react-dom": {
+      "name": "@preact/compat",
+      "version": "17.1.2",
+      "resolved": "https://registry.npmjs.org/@preact/compat/-/compat-17.1.2.tgz",
+      "integrity": "sha512-7pOZN9lMDDRQ+6aWvjwTp483KR8/zOpfS83wmOo3zfuLKdngS8/5RLbsFWzFZMGdYlotAhX980hJ75bjOHTwWg==",
+      "peerDependencies": {
+        "preact": "*"
+      }
+    },
+    "node_modules/react-router": {
+      "version": "6.4.2",
+      "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.2.tgz",
+      "integrity": "sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw==",
+      "dependencies": {
+        "@remix-run/router": "1.0.2"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "peerDependencies": {
+        "react": ">=16.8"
+      }
+    },
+    "node_modules/react-router-dom": {
+      "version": "6.4.2",
+      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.2.tgz",
+      "integrity": "sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ==",
+      "dependencies": {
+        "@remix-run/router": "1.0.2",
+        "react-router": "6.4.2"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "peerDependencies": {
+        "react": ">=16.8",
+        "react-dom": ">=16.8"
+      }
+    },
     "node_modules/readable-stream": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@@ -6434,6 +6493,11 @@
         "@prefresh/utils": "^1.1.2"
       }
     },
+    "@remix-run/router": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.2.tgz",
+      "integrity": "sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ=="
+    },
     "@trysound/sax": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@@ -9739,6 +9803,35 @@
         }
       }
     },
+    "react": {
+      "version": "npm:@preact/compat@17.1.2",
+      "resolved": "https://registry.npmjs.org/@preact/compat/-/compat-17.1.2.tgz",
+      "integrity": "sha512-7pOZN9lMDDRQ+6aWvjwTp483KR8/zOpfS83wmOo3zfuLKdngS8/5RLbsFWzFZMGdYlotAhX980hJ75bjOHTwWg==",
+      "requires": {}
+    },
+    "react-dom": {
+      "version": "npm:@preact/compat@17.1.2",
+      "resolved": "https://registry.npmjs.org/@preact/compat/-/compat-17.1.2.tgz",
+      "integrity": "sha512-7pOZN9lMDDRQ+6aWvjwTp483KR8/zOpfS83wmOo3zfuLKdngS8/5RLbsFWzFZMGdYlotAhX980hJ75bjOHTwWg==",
+      "requires": {}
+    },
+    "react-router": {
+      "version": "6.4.2",
+      "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.2.tgz",
+      "integrity": "sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw==",
+      "requires": {
+        "@remix-run/router": "1.0.2"
+      }
+    },
+    "react-router-dom": {
+      "version": "6.4.2",
+      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.2.tgz",
+      "integrity": "sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ==",
+      "requires": {
+        "@remix-run/router": "1.0.2",
+        "react-router": "6.4.2"
+      }
+    },
     "readable-stream": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",

+ 6 - 2
package.json

@@ -4,7 +4,8 @@
   "description": "An Oracle JavaScript Extension Toolkit(JET) web app",
   "config": {
     "host": "customercentral-poc.oracle.com",
-    "registry": "iad.ocir.io/cesdev"
+    "registry": "iad.ocir.io/cesdev",
+    "devApiUrl": "http://localhost:3000"
   },
   "scripts": {
     "start": "DEBUG='express:*' ojet serve",
@@ -15,7 +16,10 @@
     "deploy:container": "npm run build:container && ./deployment/scripts/ocir-login.sh $npm_package_config_registry && docker push $npm_package_config_registry/$npm_package_name:$npm_package_version"
   },
   "dependencies": {
-    "@oracle/oraclejet": "13.0.3"
+    "@oracle/oraclejet": "13.0.3",
+    "react": "npm:@preact/compat@^17.1.2",
+    "react-dom": "npm:@preact/compat@^17.1.2",
+    "react-router-dom": "^6.4.2"
   },
   "devDependencies": {
     "@oracle/ojet-cli": "^13.0.0",

+ 7 - 2
src/components/app.tsx

@@ -5,6 +5,8 @@ import { Footer } from "./footer";
 import { Header } from "./header";
 import { Content } from "./content/index";
 import { useLogin } from "../hooks/useLogin";
+import { useQuery, Plugin, NavigationItem } from "../hooks/useQuery";
+
 import Context = require("ojs/ojcontext");
 
 type Props = Readonly<{
@@ -13,7 +15,9 @@ type Props = Readonly<{
 }>;
 
 const AppComponent: FunctionComponent<Props> = ({ appName = "OCC" }) => {
-  const { userLogin, login, logout } = useLogin();
+  const { userLogin, login, logout, accessToken } = useLogin();
+  const {result: plugins} = useQuery<Plugin[]>("/plugins", accessToken);
+  const {result: navigations} = useQuery<NavigationItem[]>("/navigations", accessToken);
 
   useEffect(() => {
     Context.getPageContext().getBusyContext().applicationBootstrapComplete();
@@ -26,8 +30,9 @@ const AppComponent: FunctionComponent<Props> = ({ appName = "OCC" }) => {
           userLogin={userLogin}
           login={login}
           logout={logout}
+          navigations={navigations}
         />
-        <Content pluginUrl={userLogin ? "https://localhost:8001" : ""} />
+        <Content userLogin={userLogin} plugins={plugins} />
         <Footer />
       </div>
   );

+ 16 - 3
src/components/content/index.tsx

@@ -1,13 +1,26 @@
 import { FunctionComponent } from "preact";
+import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
+import { Plugin } from "src/hooks/useQuery";
 
 type Props = Readonly<{
-  pluginUrl: string;
+  userLogin: string;
+  plugins: Plugin[] | undefined;
 }>;
 
-export const Content: FunctionComponent<Props> = ({ pluginUrl }) => {
+export const Content: FunctionComponent<Props> = ({ userLogin, plugins }) => {
+
   return (
     <div class="oj-web-applayout-max-width oj-flex-item oj-flex">
-      {pluginUrl && <iframe src={pluginUrl} className="oj-flex-item"></iframe>}
+      {userLogin && plugins &&
+        <Router>
+          <Routes>
+            {plugins.map(p => {
+              return (<Route path={p.path} element={<iframe src={p.url} className="oj-flex-item"></iframe>}/>);
+            })}
+          </Routes>
+        </Router>
+      }
+      {!userLogin && <h1>Landing Page</h1>}
     </div>
   );
 };

+ 21 - 2
src/components/header.tsx

@@ -6,15 +6,17 @@ import { ojMenu } from "ojs/ojmenu";
 import { ojButton } from "ojs/ojbutton";
 import { FunctionComponent } from "preact";
 import { useRef, useState, useEffect } from "preact/hooks";
+import { NavigationItem } from "src/hooks/useQuery";
 
 type Props = Readonly<{
   appName: string,
   userLogin: string,
   login: () => void,
-  logout: () => void
+  logout: () => void,
+  navigations: NavigationItem[] | undefined
 }>;
 
-export const Header: FunctionComponent<Props> = ({ appName, userLogin, login, logout }) => {
+export const Header: FunctionComponent<Props> = ({ appName, userLogin, login, logout, navigations }) => {
   const mediaQueryRef = useRef<MediaQueryList>(window.matchMedia(ResponsiveUtils.getFrameworkQuery("sm-only")));
 
   const [isSmallWidth, setIsSmallWidth] = useState(mediaQueryRef.current.matches);
@@ -62,6 +64,23 @@ export const Header: FunctionComponent<Props> = ({ appName, userLogin, login, lo
             title="Application Name">
             {appName}
           </h1>
+          {navigations &&
+            <oj-menu-button id="navMenuButton" display="icons" chroming="borderless">
+              <span slot="endIcon" class="oj-ux-ico-leading-increase"></span>
+              <oj-menu id="navMenu" slot="menu" onojMenuAction={userMenuAction}>
+                {navigations.map(nav => (
+                  <oj-option value={nav.labelKey}>
+                    {nav.labelKey}
+                    {nav.children &&
+                      <oj-menu>
+                        {nav.children.map(navChild => (<oj-option value={navChild.labelKey}>{navChild.labelKey}</oj-option>))}
+                      </oj-menu>
+                    }
+                  </oj-option>
+                ))}
+              </oj-menu>
+            </oj-menu-button>
+          }
         </div>
         <div class="oj-flex-bar-end">
           <oj-toolbar>

+ 1 - 0
src/globals.d.ts

@@ -0,0 +1 @@
+declare const API_URL: string;

+ 3 - 3
src/hooks/useLogin.tsx

@@ -1,4 +1,5 @@
-import { useEffect, useMemo, useState } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
+import {apiUrl} from "../utils/environment";
 
 const parseJwt = token => {
     var base64Url = token.split('.')[1];
@@ -59,8 +60,7 @@ export const useLogin = () => {
         }
         // Get the tokens using the received authorization code
         const code = searchParams.get("code");
-        console.log("code", code);
-        fetch(`${window.location.origin}/occ/api/auth/login`, {
+        fetch(`${apiUrl}/auth/login`, {
           method: "POST",
           headers: new Headers({
             "Content-Type": "application/json"

+ 56 - 0
src/hooks/useQuery.ts

@@ -0,0 +1,56 @@
+import { useState, useEffect } from "preact/hooks";
+import { apiUrl } from "../utils/environment";
+
+export interface Plugin {
+    name: string;
+    path: string;
+    url: string;
+    bootstrapUrl?: string;
+}
+
+export interface BaseItem {
+    labelKey: string;
+    children?: NavigationItem[];
+}
+
+export interface LinkItem extends BaseItem {
+    path: string;
+    openInNewTab?: boolean;
+}
+
+export interface PluginItem extends BaseItem {
+    pluginName: string;
+    appendPath?: string;
+}
+
+export type NavigationItem = LinkItem | PluginItem;
+
+export class HttpException extends Error {
+    constructor(public path: string, public status: number) {
+        super(`HTTP call to ${path} has been failed with ${status}`);
+    }
+}
+
+export const useQuery = <T extends Plugin[] | NavigationItem[]>(path: string, accessToken: string | undefined) => {
+    const [loading, setLoading] = useState(false);
+    const [error, setError] = useState();
+    const [result, setResult] = useState<T>();
+
+    useEffect(() => {
+        if (accessToken) {
+            setLoading(true);
+            fetch(`${apiUrl}${path}`, { headers: { "Authorization": `Bearer ${accessToken}` } })
+                .then(response => {
+                    if (!response.ok) {
+                        throw new HttpException(path, response.status)
+                    }
+                    return response.json();
+                })
+                .then(setResult)
+                .catch(setError)
+                .finally(() => setLoading(false));
+        }
+    }, [accessToken]);
+
+    return { loading, result, error };
+};

+ 1 - 0
src/utils/environment.ts

@@ -0,0 +1 @@
+export const apiUrl = API_URL || `${window.location.origin}/occ/api`;

+ 6 - 0
tsconfig.json

@@ -26,6 +26,12 @@
       ],
       "oj-c/*": [
         "./node_modules/@oracle/oraclejet-core-pack/oj-c/types/*"
+      ],
+      "react": [
+        "./node_modules/preact/compat/"
+      ],
+      "react-dom": [
+        "./node_modules/preact/compat/"
       ]
     },
     "declaration": true,