@equinor/fusion-framework-dev-portal 1.0.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/dev-server.ts +96 -0
- package/package.json +67 -0
- package/rollup.config.js +16 -0
- package/src/AppLoader.tsx +121 -0
- package/src/BookMarkSideSheet.tsx +36 -0
- package/src/ContextSelector/ContextSelector.tsx +71 -0
- package/src/ContextSelector/index.ts +1 -0
- package/src/ContextSelector/useContextResolver.ts +287 -0
- package/src/EquinorLoader.tsx +24 -0
- package/src/ErrorViewer.tsx +17 -0
- package/src/FusionLogo.tsx +58 -0
- package/src/Header.Actions.tsx +35 -0
- package/src/Header.tsx +89 -0
- package/src/PersonSideSheet/index.tsx +54 -0
- package/src/PersonSideSheet/sheets/FeatureSheetContent.tsx +48 -0
- package/src/PersonSideSheet/sheets/FeatureTogglerApp.tsx +36 -0
- package/src/PersonSideSheet/sheets/FeatureTogglerPortal.tsx +30 -0
- package/src/PersonSideSheet/sheets/LandingSheetContent.tsx +52 -0
- package/src/PersonSideSheet/sheets/Styled.tsx +29 -0
- package/src/PersonSideSheet/sheets/index.ts +2 -0
- package/src/PersonSideSheet/sheets/types.ts +5 -0
- package/src/Router.tsx +79 -0
- package/src/config.ts +60 -0
- package/src/main.tsx +32 -0
- package/src/resources/fallback-photo.svg.ts +2 -0
- package/src/useAppContextNavigation.ts +104 -0
- package/src/version.ts +2 -0
- package/tsconfig.json +53 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @equinor/fusion-framework-dev-portal
|
|
2
|
+
|
|
3
|
+
## 1.0.0-next.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- [#3074](https://github.com/equinor/fusion-framework/pull/3074) [`6b034e5`](https://github.com/equinor/fusion-framework/commit/6b034e5459094cea0c0f2490335eef3092390a13) Thanks [@odinr](https://github.com/odinr)! - This release introduces a new package, `@equinor/fusion-framework-dev-portal`, as part of a refactor of the `@equinor/fusion-framework-cli`.
|
|
8
|
+
The refactor moves specific functionality and code related to the development portal into its own dedicated package to improve modularity and maintainability.
|
|
9
|
+
|
|
10
|
+
Additionally, this update includes improved documentation and examples for better user guidance.
|
|
11
|
+
|
|
12
|
+
This package is a small part of the refactoring of the `@equinor/fusion-framework-cli` and is not intended for direct use in production.
|
|
13
|
+
The main purpose of this package is to provide a development portal for the Fusion framework, allowing developers to easily set up and test their applications in a local environment.
|
|
14
|
+
|
|
15
|
+
Even though this package can be used as standalone, it is recommended to use the `@equinor/fusion-framework-dev-server` package for a more complete development experience.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [#3074](https://github.com/equinor/fusion-framework/pull/3074) [`6b034e5`](https://github.com/equinor/fusion-framework/commit/6b034e5459094cea0c0f2490335eef3092390a13) Thanks [@odinr](https://github.com/odinr)! - update Vite to 6.3.5
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Equinor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
## Dev Portal
|
|
2
|
+
|
|
3
|
+
This package provides a development portal for the Fusion framework.
|
|
4
|
+
|
|
5
|
+
This Portal only contains the bare minimum to get started with the Fusion Application development. Visuals might differ from the production portal.
|
|
6
|
+
|
|
7
|
+
It is recommended to use the `@equinor/fusion-framework-dev-server` package for a more complete development experience.
|
|
8
|
+
This package is a small part of the refactoring of the `@equinor/fusion-framework-cli` and is not intended for direct use in production.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
pnpm add @equinor/fusion-framework-dev-portal
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Environment Variables
|
|
17
|
+
|
|
18
|
+
The following environment variables must be set in `.env` or in the environment variables of the server:
|
|
19
|
+
|
|
20
|
+
- FUSION_MSAL_CLIENT_ID: The client ID for MSAL authentication.
|
|
21
|
+
- FUSION_MSAL_TENANT_ID: The tenant ID for MSAL authentication.
|
|
22
|
+
- FUSION_SERVICE_DISCOVERY_URL: The URL for service discovery.
|
|
23
|
+
- FUSION_SERVICE_SCOPE: The scopes for the service.
|
|
24
|
+
|
|
25
|
+
## Development
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
pnpm start
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { render } from "@equinor/fusion-framework-dev-portal";
|
|
35
|
+
|
|
36
|
+
const el = document.createElement("div");
|
|
37
|
+
el.id = "dev-portal";
|
|
38
|
+
document.body.appendChild(el);
|
|
39
|
+
|
|
40
|
+
const framework = /** Fusion Framework, [Service Discovery, MSAL] */;
|
|
41
|
+
|
|
42
|
+
render(el, {ref: framework});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Usage with `@equinor/fusion-framework-dev-server`
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { createDevServer } from "@equinor/fusion-framework-dev-server";
|
|
49
|
+
|
|
50
|
+
const portalId = '@equinor/fusion-framework-dev-portal';
|
|
51
|
+
const devServer = await createDevServer({
|
|
52
|
+
spa: {
|
|
53
|
+
templateEnv: {
|
|
54
|
+
portal: {
|
|
55
|
+
id: portalId,
|
|
56
|
+
tag: 'latest',
|
|
57
|
+
}
|
|
58
|
+
/** --- Add any other environment variables you need here --- */
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
api: {
|
|
62
|
+
serviceDiscoveryUrl: "https://location.of.your.service.discovery",
|
|
63
|
+
processServices: /** generate proxy routes */
|
|
64
|
+
routes: [
|
|
65
|
+
{
|
|
66
|
+
// intercept for the dev-portal manifest
|
|
67
|
+
match: `/PROXY_SERVICE_KEY/PROXY_PATH/${options.portal}{@:tag}`,
|
|
68
|
+
middleware: async (req, res) => {
|
|
69
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
70
|
+
// resolve the local path to the dev-portal
|
|
71
|
+
// __CWD__/node_modules/@equinor/fusion-framework-dev-portal/dist/main.js
|
|
72
|
+
const path = fileURLToPath('/@fs' + import.meta.resolve(portalId));
|
|
73
|
+
const manifest = {
|
|
74
|
+
build: {
|
|
75
|
+
entrypoint: new URL(path, req.headers.referer),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
res.end(JSON.stringify(manifest));
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
|
package/dev-server.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
import { createDevServer, processServices } from '@equinor/fusion-framework-dev-server';
|
|
3
|
+
|
|
4
|
+
import { name } from './package.json' assert { type: 'json' };
|
|
5
|
+
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
const clientId = process.env.FUSION_MSAL_CLIENT_ID;
|
|
9
|
+
const tenantId = process.env.FUSION_MSAL_TENANT_ID;
|
|
10
|
+
const serviceDiscoveryUrl = process.env.FUSION_SERVICE_DISCOVERY_URL;
|
|
11
|
+
const serviceScopes = JSON.parse(process.env.FUSION_SERVICE_SCOPE || '[]') as string[];
|
|
12
|
+
|
|
13
|
+
if (!clientId) {
|
|
14
|
+
throw new Error('FUSION_MSAL_CLIENT_ID is not set');
|
|
15
|
+
}
|
|
16
|
+
if (!tenantId) {
|
|
17
|
+
throw new Error('FUSION_MSAL_TENANT_ID is not set');
|
|
18
|
+
}
|
|
19
|
+
if (!serviceDiscoveryUrl) {
|
|
20
|
+
throw new Error('SERVICE_DISCOVERY_URL is not set');
|
|
21
|
+
}
|
|
22
|
+
if (serviceScopes.length === 0) {
|
|
23
|
+
throw new Error('SERVICE_SCOPES is not set');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
(async () => {
|
|
27
|
+
const devServer = await createDevServer({
|
|
28
|
+
spa: {
|
|
29
|
+
templateEnv: {
|
|
30
|
+
portal: {
|
|
31
|
+
id: name,
|
|
32
|
+
},
|
|
33
|
+
title: 'Fusion SPA',
|
|
34
|
+
serviceDiscovery: {
|
|
35
|
+
url: '/@fusion-services',
|
|
36
|
+
scopes: serviceScopes,
|
|
37
|
+
},
|
|
38
|
+
msal: {
|
|
39
|
+
clientId,
|
|
40
|
+
tenantId,
|
|
41
|
+
redirectUri: '/authentication/login-callback',
|
|
42
|
+
requiresAuth: 'true',
|
|
43
|
+
},
|
|
44
|
+
serviceWorker: {
|
|
45
|
+
resources: [
|
|
46
|
+
{
|
|
47
|
+
url: '/apps-proxy',
|
|
48
|
+
rewrite: '/@fusion-api/apps',
|
|
49
|
+
scopes: serviceScopes,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
url: '/help-proxy',
|
|
53
|
+
rewrite: '/@fusion-api/help',
|
|
54
|
+
scopes: serviceScopes,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
api: {
|
|
61
|
+
serviceDiscoveryUrl,
|
|
62
|
+
processServices: (dataResponse, route) => {
|
|
63
|
+
const { data, routes } = processServices(dataResponse, route);
|
|
64
|
+
return {
|
|
65
|
+
data: data.concat({
|
|
66
|
+
key: 'portals',
|
|
67
|
+
name: 'Portal Service - MOCK',
|
|
68
|
+
uri: '/@fusion-api/portals',
|
|
69
|
+
}),
|
|
70
|
+
routes,
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
routes: [
|
|
74
|
+
{
|
|
75
|
+
match: `/portals/portals/${name}{@:tag}`,
|
|
76
|
+
middleware: async (req, res) => {
|
|
77
|
+
const url = new URL('/src/main.tsx', req.headers.referer);
|
|
78
|
+
res.writeHead(200, {
|
|
79
|
+
'content-type': 'application/json',
|
|
80
|
+
});
|
|
81
|
+
res.end(
|
|
82
|
+
JSON.stringify({
|
|
83
|
+
build: {
|
|
84
|
+
config: {},
|
|
85
|
+
entrypoint: url,
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
await devServer.listen();
|
|
95
|
+
devServer.printUrls();
|
|
96
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@equinor/fusion-framework-dev-portal",
|
|
3
|
+
"version": "1.0.0-next.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "./dist/main.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/main.js"
|
|
9
|
+
},
|
|
10
|
+
"directories": {
|
|
11
|
+
"dist": "dist"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@equinor/eds-core-react": "^0.43.0",
|
|
21
|
+
"@equinor/eds-icons": "^0.22.0",
|
|
22
|
+
"@equinor/eds-tokens": "^0.9.2",
|
|
23
|
+
"@equinor/fusion-react-context-selector": "^1.0.4",
|
|
24
|
+
"@equinor/fusion-react-progress-indicator": "^0.3.0",
|
|
25
|
+
"@equinor/fusion-react-side-sheet": "1.3.6",
|
|
26
|
+
"@equinor/fusion-react-styles": "^0.6.4",
|
|
27
|
+
"@equinor/fusion-wc-chip": "^1.2.2",
|
|
28
|
+
"@equinor/fusion-wc-person": "^3.1.8",
|
|
29
|
+
"@material-ui/styles": "^4.11.5",
|
|
30
|
+
"@rollup/plugin-commonjs": "^28.0.3",
|
|
31
|
+
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
32
|
+
"@rollup/plugin-typescript": "^12.1.2",
|
|
33
|
+
"@types/dotenv": "^8.2.3",
|
|
34
|
+
"@types/react": "^18.2.50",
|
|
35
|
+
"@types/react-dom": "^18.2.7",
|
|
36
|
+
"dotenv": "^16.5.0",
|
|
37
|
+
"react": "^18.2.0",
|
|
38
|
+
"react-dom": "^18.2.0",
|
|
39
|
+
"react-router-dom": "^6.15.0",
|
|
40
|
+
"rollup": "^4.39.0",
|
|
41
|
+
"rxjs": "^7.8.1",
|
|
42
|
+
"styled-components": "^6.0.7",
|
|
43
|
+
"tsx": "^4.19.3",
|
|
44
|
+
"typescript": "^5.8.2",
|
|
45
|
+
"vite": "^6.3.5",
|
|
46
|
+
"@equinor/fusion-framework": "^7.3.13-next.0",
|
|
47
|
+
"@equinor/fusion-framework-app": "^9.3.15-next.0",
|
|
48
|
+
"@equinor/fusion-framework-module-bookmark": "^2.1.9",
|
|
49
|
+
"@equinor/fusion-framework-module-app": "^6.1.13",
|
|
50
|
+
"@equinor/fusion-framework-dev-server": "^1.0.0-next.0",
|
|
51
|
+
"@equinor/fusion-framework-module-context": "^6.0.3",
|
|
52
|
+
"@equinor/fusion-framework-module-feature-flag": "^1.1.18",
|
|
53
|
+
"@equinor/fusion-framework-module-navigation": "^5.0.2",
|
|
54
|
+
"@equinor/fusion-framework-module-services": "^6.0.2",
|
|
55
|
+
"@equinor/fusion-framework-react": "^7.4.13-next.0",
|
|
56
|
+
"@equinor/fusion-framework-react-components-bookmark": "^1.0.21-next.0",
|
|
57
|
+
"@equinor/fusion-framework-react-components-people-provider": "^1.5.18-next.0",
|
|
58
|
+
"@equinor/fusion-observable": "^8.4.9",
|
|
59
|
+
"@equinor/fusion-framework-react-module-bookmark": "^4.0.4-next.0",
|
|
60
|
+
"@equinor/fusion-query": "^5.2.8"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"start": "tsx dev-server.ts",
|
|
64
|
+
"build": "rollup -c rollup.config.js",
|
|
65
|
+
"build:clean": "rm -rf dist && rm -f tsconfig.tsbuildinfo && pnpm build"
|
|
66
|
+
}
|
|
67
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import resolve from '@rollup/plugin-node-resolve';
|
|
2
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
3
|
+
import typescript from '@rollup/plugin-typescript';
|
|
4
|
+
|
|
5
|
+
/** @type {import('rollup').RollupOptions} */
|
|
6
|
+
export default [
|
|
7
|
+
{
|
|
8
|
+
input: 'src/main.tsx',
|
|
9
|
+
output: {
|
|
10
|
+
file: 'dist/main.js', // Output bundle file
|
|
11
|
+
format: 'esm',
|
|
12
|
+
sourcemap: true,
|
|
13
|
+
},
|
|
14
|
+
plugins: [typescript(), resolve(), commonjs()],
|
|
15
|
+
},
|
|
16
|
+
];
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Subscription } from 'rxjs';
|
|
4
|
+
import { last } from 'rxjs/operators';
|
|
5
|
+
|
|
6
|
+
import { useFramework } from '@equinor/fusion-framework-react';
|
|
7
|
+
|
|
8
|
+
import { useObservableState } from '@equinor/fusion-observable/react';
|
|
9
|
+
|
|
10
|
+
import { AppManifestError } from '@equinor/fusion-framework-module-app/errors.js';
|
|
11
|
+
|
|
12
|
+
import { ErrorViewer } from './ErrorViewer';
|
|
13
|
+
import type { AppModule } from '@equinor/fusion-framework-module-app';
|
|
14
|
+
import EquinorLoader from './EquinorLoader';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* React Functional Component for handling current application
|
|
18
|
+
*
|
|
19
|
+
* this component will set the current app by provided appKey.
|
|
20
|
+
* when the appKey changes, this component will try to initialize the referred application
|
|
21
|
+
* and render it.
|
|
22
|
+
*/
|
|
23
|
+
export const AppLoader = (props: { readonly appKey: string }) => {
|
|
24
|
+
const { appKey } = props;
|
|
25
|
+
const fusion = useFramework<[AppModule]>();
|
|
26
|
+
|
|
27
|
+
/** reference of application section/container */
|
|
28
|
+
const ref = useRef<HTMLElement>(null);
|
|
29
|
+
|
|
30
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
31
|
+
const [error, setError] = useState<Error | undefined>();
|
|
32
|
+
|
|
33
|
+
// TODO change to `useCurrentApp`
|
|
34
|
+
/** observe and use the current selected application from framework */
|
|
35
|
+
const { value: currentApp } = useObservableState(
|
|
36
|
+
useMemo(() => fusion.modules.app.current$, [fusion.modules.app]),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
/** when appKey property change, assign it to current */
|
|
41
|
+
fusion.modules.app.setCurrentApp(appKey);
|
|
42
|
+
}, [appKey, fusion]);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
/** flag that application is loading */
|
|
46
|
+
setLoading(true);
|
|
47
|
+
|
|
48
|
+
/** clear previous errors */
|
|
49
|
+
setError(undefined);
|
|
50
|
+
|
|
51
|
+
/** create a teardown of load */
|
|
52
|
+
const subscription = new Subscription();
|
|
53
|
+
|
|
54
|
+
/** make sure that initialize is canceled and disposed if current app changes */
|
|
55
|
+
subscription.add(
|
|
56
|
+
currentApp
|
|
57
|
+
?.initialize()
|
|
58
|
+
.pipe(last())
|
|
59
|
+
.subscribe({
|
|
60
|
+
next: ({ manifest, script, config }) => {
|
|
61
|
+
/** generate basename for application */
|
|
62
|
+
const [basename] = window.location.pathname.match(/\/?apps\/[a-z|-]+(\/)?/g) ?? [''];
|
|
63
|
+
|
|
64
|
+
/** create a 'private' element for the application */
|
|
65
|
+
const el = document.createElement('div');
|
|
66
|
+
if (!ref.current) {
|
|
67
|
+
throw Error('Missing application mounting point');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
ref.current.appendChild(el);
|
|
71
|
+
|
|
72
|
+
/** extract render callback function from javascript module */
|
|
73
|
+
const render = script.renderApp ?? script.default;
|
|
74
|
+
|
|
75
|
+
/** add application teardown to current render effect teardown */
|
|
76
|
+
subscription.add(render(el, { fusion, env: { basename, config, manifest } }));
|
|
77
|
+
|
|
78
|
+
/** remove app element when application unmounts */
|
|
79
|
+
subscription.add(() => el.remove());
|
|
80
|
+
},
|
|
81
|
+
complete: () => {
|
|
82
|
+
/** flag that application is no longer loading */
|
|
83
|
+
setLoading(false);
|
|
84
|
+
},
|
|
85
|
+
error: (err) => {
|
|
86
|
+
/** set error if initialization of application fails */
|
|
87
|
+
setError(err);
|
|
88
|
+
},
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
/** teardown application when hook unmounts */
|
|
93
|
+
return () => subscription.unsubscribe();
|
|
94
|
+
}, [fusion, currentApp]);
|
|
95
|
+
|
|
96
|
+
if (error) {
|
|
97
|
+
if (error.cause instanceof AppManifestError) {
|
|
98
|
+
return (
|
|
99
|
+
<div>
|
|
100
|
+
<h2>🔥 Failed to load application manifest 🤬</h2>
|
|
101
|
+
<h3>{error.cause.type}</h3>
|
|
102
|
+
<ErrorViewer error={error} />
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return (
|
|
107
|
+
<div>
|
|
108
|
+
<h2>🔥 Failed to load application 🤬</h2>
|
|
109
|
+
<ErrorViewer error={error} />
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<section id="application-content" ref={ref} style={{ display: 'contents' }}>
|
|
116
|
+
{loading && <EquinorLoader text="Loading Application" />}
|
|
117
|
+
</section>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export default AppLoader;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Button, Icon } from '@equinor/eds-core-react';
|
|
2
|
+
import { Bookmark } from '@equinor/fusion-framework-react-components-bookmark';
|
|
3
|
+
import { useBookmarkComponentContext } from '@equinor/fusion-framework-react-components-bookmark';
|
|
4
|
+
import { SideSheet } from '@equinor/fusion-react-side-sheet';
|
|
5
|
+
|
|
6
|
+
type BookmarkSideSheetProps = {
|
|
7
|
+
readonly isOpen: boolean;
|
|
8
|
+
readonly onClose: VoidFunction;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const BookmarkSideSheet = ({ isOpen, onClose }: BookmarkSideSheetProps) => {
|
|
12
|
+
const { provider, showCreateBookmark } = useBookmarkComponentContext();
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<SideSheet isOpen={isOpen} onClose={onClose} isDismissable={true} enableFullscreen={true}>
|
|
16
|
+
<SideSheet.Indicator color={'#258800'} />
|
|
17
|
+
<SideSheet.Title title="Bookmarks" />
|
|
18
|
+
<SideSheet.SubTitle subTitle={'Application bookmarks'} />
|
|
19
|
+
<SideSheet.Actions>
|
|
20
|
+
<Button
|
|
21
|
+
disabled={!provider?.canCreateBookmarks}
|
|
22
|
+
variant="ghost"
|
|
23
|
+
onClick={() => {
|
|
24
|
+
showCreateBookmark();
|
|
25
|
+
onClose();
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
<Icon name="add" /> Add Bookmark
|
|
29
|
+
</Button>
|
|
30
|
+
</SideSheet.Actions>
|
|
31
|
+
<SideSheet.Content>
|
|
32
|
+
<Bookmark />
|
|
33
|
+
</SideSheet.Content>
|
|
34
|
+
</SideSheet>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ContextProvider,
|
|
4
|
+
ContextSearch,
|
|
5
|
+
type ContextSearchProps,
|
|
6
|
+
type ContextSelectEvent,
|
|
7
|
+
ContextClearEvent,
|
|
8
|
+
} from '@equinor/fusion-react-context-selector';
|
|
9
|
+
import { useContextResolver } from './useContextResolver';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* See fusion-react-component storybook for available attributes
|
|
13
|
+
* @link https://equinor.github.io/fusion-react-components/?path=/docs/data-contextselector--component
|
|
14
|
+
* @returns JSX element
|
|
15
|
+
*/
|
|
16
|
+
export const ContextSelector = (props: ContextSearchProps): JSX.Element | null => {
|
|
17
|
+
const {
|
|
18
|
+
resolver,
|
|
19
|
+
provider,
|
|
20
|
+
currentContext: [selectedContextItem],
|
|
21
|
+
} = useContextResolver();
|
|
22
|
+
|
|
23
|
+
/** callback handler for context selector, when context is changed or cleared */
|
|
24
|
+
const onContextSelect = useCallback(
|
|
25
|
+
(e: Event | ContextSelectEvent) => {
|
|
26
|
+
if (provider) {
|
|
27
|
+
if (e.type === 'select') {
|
|
28
|
+
const ev = e as unknown as ContextSelectEvent;
|
|
29
|
+
if (ev.nativeEvent.detail.selected.length) {
|
|
30
|
+
provider.contextClient.setCurrentContext(ev.nativeEvent.detail.selected[0].id);
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
provider.clearCurrentContext();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
[provider],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Clears context when ctx has been cleared outside the selector.
|
|
42
|
+
*/
|
|
43
|
+
const clearEvent = useMemo(() => new ContextClearEvent({ date: Date.now() }), []);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!selectedContextItem) {
|
|
46
|
+
document.dispatchEvent(clearEvent);
|
|
47
|
+
}
|
|
48
|
+
}, [clearEvent, selectedContextItem]);
|
|
49
|
+
|
|
50
|
+
if (!resolver) return null;
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div style={{ flex: 1, maxWidth: '480px' }}>
|
|
54
|
+
<ContextProvider resolver={resolver}>
|
|
55
|
+
<ContextSearch
|
|
56
|
+
id="context-selector-cli-header"
|
|
57
|
+
placeholder={props.placeholder ?? 'Search for context'}
|
|
58
|
+
initialText={props.initialText ?? 'Start typing to search'}
|
|
59
|
+
dropdownHeight={props.dropdownHeight ?? '300px'}
|
|
60
|
+
variant={props.variant ?? 'header'}
|
|
61
|
+
onSelect={(e: ContextSelectEvent) => onContextSelect(e)}
|
|
62
|
+
selectTextOnFocus={true}
|
|
63
|
+
previewItem={selectedContextItem}
|
|
64
|
+
onClearContext={onContextSelect}
|
|
65
|
+
/>
|
|
66
|
+
</ContextProvider>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default ContextSelector;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ContextSelector } from './ContextSelector';
|